Every realtime feature uses the same pattern: a subscribe* method takes handlers and returns an
unsubscribe function. One shared WebSocket multiplexes all channels, so twenty streams still cost
one connection (two when public and private are both active).
The subscription contract
const unsubscribe = client.orders.subscribe({
accountId,
onEvent: (order) => console.log(order), // required: each published event
onOpen: () => console.log("subscribed"), // optional: (re)connected
onClose: () => console.log("disconnected"), // optional: connection dropped
onError: (ctx) => console.error(ctx.channel, ctx.type, ctx.error), // optional
});
unsubscribe(); // idempotent; releases the channel when the last consumer leavesHandlers get fully parsed, typed objects. Same shapes as the request methods, decimal strings included.
The returned unsubscribe function does not mean the channel is ready. The first onOpen confirms
the subscription. Wait for it before a write that depends on observing the stream; it can run again
after reconnect, so keep one-time writes outside that callback.
onError receives SdkSubscriptionErrorContext: channel, error type (subscription,
connection token, publication handler, and so on), and the underlying error. A terminal server
disconnect or unsubscribe reports type as "disconnected" or "unsubscribed", with error: { code, message }, then calls onClose. Errors in your handlers are isolated and routed
here too. A throwing onEvent never kills the transport.
Subscriptions are shared. Two subscribe calls on the same channel attach to one underlying
subscription. Each gets every event.
What you can stream
| Stream | Method | Auth |
|---|---|---|
| Candles | client.candles.subscribe / subscribeInts | public |
| Public trades | client.marketData.subscribeTrades | public |
| Market overview | client.marketOverview.subscribe | public |
| Order book | client.orderbook.createSubscription / subscribe | public |
| Heatmap buckets | client.heatmap.subscribeLive | public |
| Lifecycle flows | client.lifecycle.subscribeOpenFlows / subscribeFlowDetail | public or private |
| Zipped asset supply | client.zipper.subscribeZippedAssetSupply | public |
| Your orders | client.orders.subscribe | private |
| Your triggers | client.triggers.subscribe / subscribeEvents | private |
| Your trades | client.trades.subscribe | private |
| Balances | client.balances.subscribe | private |
| Ledger transfers | client.transfers.subscribe | private |
| Subaccounts / API keys / policies | client.subaccounts.subscribe, client.apiKeys.subscribe, client.subaccounts.policies.subscribePolicies | private |
| Address book invalidations | client.addressBook.subscribeViewInvalidations | private |
| Profile identity | client.auth.profile.subscribeIdentity | public |
Private channels authenticate with the client's auth provider (JWT or API key). Without usable
auth, a private subscription calls onError when supplied; otherwise subscribe throws.
The SDK reads the current JWT provider value for every realtime token request. Let the provider read its current session state rather than capturing a JWT when the client is created, so refresh and logout take effect for later subscriptions.
Snapshot-then-stream
State streams (order book, market overview) first fetch a snapshot over HTTP, buffer
publications that arrive meanwhile, then apply deltas in order. After a reconnect or an observed
sequence gap, they refetch. Your onEvent sees a consistent view relative to the last applied
snapshot or delta.
That is the whole recovery contract. A feed that stays subscribed but goes silent does not fire onError, does not reconnect, and does not refetch. The SDK cannot tell a dead feed from a quiet
market. Applications that need continuity must reconcile after connection gaps and after a
connected feed stops publishing.
const subscription = client.orderbook.createSubscription({
symbolId,
depth: 10,
onEvent: (book) => render(book),
});
subscription.unsubscribe();For the order book, depth is any integer in [1, 500] (values above 500 clamp to 500). The
SDK maps it onto a published channel and slices levels back to the depth you asked for. Emitted
events carry that requested depth. Continuity across a silent-but-connected book is still your
job: track the last onEvent and resync with orderbook.get() when it goes idle. See Order book.
Connection management
Shared realtime client: client.realtime.
client.realtime.isConnected; // any active websocket?
client.realtime.activeChannels; // number of attached channels
client.realtime.disconnect(); // tear down everything
client.realtime.disconnectPrivate(); // drop only the private connection (e.g. on logout)You rarely call these. Connections open lazily with the first subscription and close when the
last consumer unsubscribes. auth.logout() already disconnects private channels.
Terminal server closures remove their channels before onError and onClose run. Reconcile from
an authoritative read, then subscribe again from either callback if the user has refreshed the
required credentials or permissions. Calling an unsubscribe handle yourself stays silent.
Ordering and reference data
Events with prices or quantities need catalog scale data to decode. Subscriptions gate on
readiness: events that arrive early are queued and flushed in order once the catalog is ready.
You never see a partially decoded event. That queue is bounded. If the catalog never becomes ready,
or the backlog outgrows the bound, the subscription reports it through onError and stops instead
of staying connected with a hole in the stream.
For entity streams (orders, balances, trades), pair the stream with a snapshot read
(listOpen, list, and so on) when you need a complete starting state. These streams do not
promise replay after a disconnect or an ambiguous mutation, so reconcile from an authoritative read
before relying on local state again. The same rule applies to a connected stream that goes silent: onOpen staying up is not proof the feed is live.