Polyester WebSocket subscriptions deliver live market and account updates without polling. Use them alongside REST or ConnectRPC: unary APIs establish or reconcile state, while WebSocket channels deliver subsequent changes.
Available streams
- market trades, candles, and order-book changes
- market overview and other public market data
- other public updates listed in Realtime channels
- order updates, on the private WebSocket
- trigger and user-trade updates
- balances and ledger transfers
- Subaccount, API-key, policy, and account events
Connection flow
These steps open the realtime connection that carries market data and every private stream except order updates. Order updates have their own private WebSocket; see Private order updates.
Prepare private authentication
Public clients skip this step. Private clients use a JWT session or Ed25519 API-key identity to
obtain a connection token from /v1/rt/token. The token is valid for 5 minutes from issuance. The
response includes expires_unix, the authoritative expiration timestamp in Unix seconds.
Open the realtime connection
Connect to the environment API host at:
/connection/websocketPrivate clients include the connection token. Open the connection through the selected SDK's realtime client or a custom WebSocket client.
Authorize and subscribe
Before subscribing to a private channel, obtain its channel-bound token from /v1/rt/subscribe.
Subscription tokens are valid for 1 minute from issuance and the response includes their expires_unix value. Public subscriptions need no token. Realtime tokens do not replace
authentication for unary write methods.
Right after a policy is created for or attached to a Subaccount, /v1/rt/subscribe can briefly
answer HTTP 503 with {"code":"UPSTREAM_ERROR"} for that Subaccount's channels while the new policy
takes effect. Retry with backoff for a few seconds. HTTP 403 remains a final denial.
Decode the matching payload
Use the relevant SDK product method or the exact channel name and payload schema for a custom client. Encoding support is channel-specific.
Reconcile after reconnecting
Restore subscriptions, reload the relevant snapshot or history through REST or ConnectRPC, and reconcile local state before applying new updates.
expires_unix time. Refreshing a
realtime token still requires a valid JWT session or API-key identity.Delivery and recovery
Within an active subscription, realtime delivery is at least once. Reconnects and backend rebalances can cause duplicate messages, while a disconnected client can miss live messages.
Apply state and configuration snapshots idempotently as replacement state. Use revisions when the schema exposes them, and allow repeated invalidations to trigger the same safe refresh. For event streams, deduplicate with stable identifiers and ordering fields from the payload, never with the number or arrival position of a message. See Event streams for each stream's identity.
After reconnecting, reload the relevant snapshot or history through REST or ConnectRPC before resuming live application. Unary APIs remain the authority for reconnect recovery.
Event streams
Four channels carry events: market trades, your trades, your ledger transfers, and your trigger events. Every publication on them is an ordered array of events. A single event arrives as an array of one.
- JSON channels publish
{"events":[…]}. Each element is the event object of that stream. Ledger transfer events also carrytransferId. - Protobuf channels publish the stream's batch message. Its field 1,
events, repeats the event message. - A publication carries 1 to 256 events and at most 64 KiB.
Each stream's channel names, encodings, and batch message are in the streams reference, linked below. This list adds what to deduplicate on and where to reload from:
- Market trades · Identity:
matchId
Reload over REST:GET /v1/spot/markets/{symbol}/trades - Your trades · Identity:
matchId,orderId
Reload over REST:GET /v1/spot/trades - Ledger transfers · Identity:
transferId,account,isDebit
Reload over REST:GET /v1/transfers - Trigger events · Identity:
triggerId,eventType,childSeq,tsNs
Reload over REST:GET /v1/triggers, thenGET /v1/triggers/{trigger_id}/events
transferId is an unsigned 128-bit integer as a decimal string, shared by both legs of a transfer. isDebit is omitted on credit legs, so treat a missing isDebit as false. In Protobuf, the same
identities use the snake_case fields, and a ledger transfer leg is identified by transfer_id, account_code, and is_debit. Transfer rows returned by GET /v1/transfers carry the same transferId.
Apply these rules to every event stream:
- Apply events in array order, and arrays in publication order. Within one market, events arrive in the order they happened. On an account stream, events of different markets have no defined order relative to each other.
- Upsert by event identity. An event whose identity you already hold replaces the earlier copy. You
can receive an event again after a reconnect or a service restart, possibly in a different array.
Applying it again changes nothing. A transfer leg can arrive first without
flowIdand later with it. - Subscribe with recovery. If the subscription reports that missed publications could not be
recovered (
recovered=false, or a gap in the stream position), reload the stream's recent events over REST with the endpoint in the list above. Then keep applying live arrays with rule 2. - Other channels are unchanged. Balances, trigger state, and account and identity channels publish one current view per publication.
Subaccount updates
The Protobuf channel private:auth:subaccounts:<rootAccountPublicId>:proto (Subaccount updates) sends an auth.v1.Subaccount whenever a Subaccount the account owns or is a member of changes. When the
account loses access to a Subaccount, for example after its owner removes the account as a member,
the update carries removed: true: remove that Subaccount from your list. Read responses never set removed; they list only Subaccounts the caller can access.
Losing access also ends your subscriptions to that Subaccount's channels. If the server ends such a
subscription while the latest update lists the Subaccount with a role and removed: false,
subscribe again: access is checked again on subscribe.
Private order updates
Your order updates do not use the realtime connection. They arrive on the private WebSocket, wss://<environment API host>/v1/ws/private, which sends a snapshot of each account's orders and
then every change as a unit with a resumable cursor. It authenticates with the same connect and
subscribe tokens from /v1/rt/token and /v1/rt/subscribe, and offers JSON and Protobuf encodings.
The realtime connection at /connection/websocket does not serve order channels.
The private WebSocket page is the full reference: tokens, frames, units, order versions, sync,
resume and resync, limits, and close codes.
Choose a payload guide
- Use WebSocket JSON for JSON channels and custom JSON-oriented clients.
- Use WebSocket Protobuf for typed channels and official SDK realtime clients.
Encoding support remains channel-specific. Do not assume that every channel has both variants.
Treat WebSocket delivery as the live update path, not the sole durable record for application state.
See the Connectivity matrix for official SDK repositories and the TypeScript streaming guide for an implementation example.