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, plus the private orders WebSocket
for orders.subscribe).
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, always an Error.
Server and WebSocket failures arrive as RealtimeError with the Centrifugo code on realtimeCode. When error.retryable is true the realtime client is already retrying (for
example code 109, token expired), so you can skip reporting it. A terminal server disconnect or
unsubscribe reports type as "disconnected" or "unsubscribed" with a non-retryable RealtimeError, 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.
Public trades, your trades, ledger transfers, and trigger events arrive as batched publications. onEvent still fires once per event, in order, and a malformed event reaches onError without
dropping the rest of its batch.
Your orders
client.orders.subscribe streams over the private WebSocket (/v1/ws/private). Each changed
order arrives complete through onEvent; keep the copy with the higher version. Pass onSnapshot to receive the account's full live view when the subscription starts and whenever
the server replaces it, and onStale to show when that view is temporarily behind. Gaps, expired
tokens, and reconnects resume from the last applied position on their own. accountId must be the
account's base58 public ID. See Orders.
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. While a book is being built or was invalidated, its snapshot
reaches onError as a ServiceUnavailableError and is retried; the first onEvent arrives once
the book is available, never as an empty book. 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 (balances, trades, transfers), pair the stream with a snapshot read (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. orders.subscribe is the exception: onSnapshot delivers the
live view, and the stream resumes after interruptions, including a dropped heartbeat.