client.realtime is the shared RealtimeClient every service's subscribe* method goes
through. You normally use the service-level methods (typed events, catalog-gated decoding); this
page documents the underlying client for advanced use.
The handler contract (service-level)
Every service subscription input extends BaseSubscribeInput:
interface BaseSubscribeInput<T> {
onEvent: (event: T) => void; // required
onOpen?: () => void; // subscribed / resubscribed
onClose?: () => void; // connection lost (auto-reconnects)
onError?: (ctx: SdkSubscriptionErrorContext) => void;
}The return value is always an idempotent unsubscribe function () => void.
SdkSubscriptionErrorContext
| Field | Meaning |
|---|---|
channel | The channel the error belongs to. |
type | Where it arose: subscription failure, connection-token fetch, publication handler, snapshot fetch, … |
error | The underlying error value. |
Errors thrown by your handlers are caught and routed here — a bad onEvent never breaks the
connection.
RealtimeClient API
| Member | Signature | Notes |
|---|---|---|
subscribe | (channel, handlers) => () => void | JSON-decoded publications. |
connectChannel | ({ channel, schema, onPublication, onConnected?, onDisconnected?, onError? }) => () => void | Protobuf-decoded via a @bufbuild/protobuf schema. |
connectProtoChannel | same → () => void | Variant used by SDK services for :proto channels. |
disconnect | () => void | Tear down all connections and subscriptions. |
disconnectPrivate | () => void | Drop only the private connection (used by auth.logout). |
isConnected | boolean | Any live socket. |
activeChannels | number | Attached channels. |
totalConsumers | number | Handler attachments across channels (shared channels count each consumer). |
Subscriptions are refcounted and shared: multiple consumers of one channel share a single server subscription; the channel detaches when the last consumer unsubscribes.
Public vs. private connections
Channel names determine the connection:
public:…channels ride an unauthenticated connection.private:…channels ride a separate connection whose token requests carry the client's auth headers (JWT bearer or Ed25519 signature). Tokens are fetched from the environment's realtime token/subscribe endpoints.
Channel formats you'll see in practice:
public:spot:market:candles:{timeframe}:{symbolId}:proto
public:spot:orderbook:deltas:depth:{depth}:{symbolId}:proto
private:spot:orders:{accountId}:proto
private:ledger:balances:{accountId}:proto
private:auth:api-keys:{accountId}:protoTreat these as internal detail — the service methods construct them for you.
Loading and SSR behavior
The WebSocket engine (Centrifuge protobuf build, ~300 KB) is dynamically imported when the first subscription attaches — apps that never subscribe never load it. During SSR, subscribing rejects with an error: fetch snapshots on the server, subscribe in the browser.
Reconnection
Reconnects are automatic with token refresh. On reconnect, onOpen fires again;
snapshot-then-stream subscriptions (order book, market overview) additionally refetch their
snapshot so state never drifts. See Streaming for the
consumer-facing behavior.
Custom realtime auth
Override how WebSocket auth headers are derived (independently of HTTP auth) with the client's realtime config:
const client = new PolyesterClient({
environment,
realtime: {
getAuthHeaders: async ({ url, method }) => ({ authorization: `Bearer ${await mint()}` }),
hasAuth: () => true,
},
});