Deep Dive — WebSocket Sessions
The SDK manages WebSocket connections, reconnection, and subscription lifecycle automatically. Understanding the session architecture helps you reason about connection counts, reconnect behavior, and resource cleanup.
Session architecture
Every WebSocket stream you open through client.websocket goes through a WsSession, which owns a WsTransport (the raw socket). The SDK uses three session categories:
Shared session
Public streams and authenticated streams share one underlying WsSession (and therefore one WsTransport). These streams multiplex over a single connection:
trades(symbol)— public trade streambookTicker(symbol)— public best bid/askdepthUpdates(symbol)— public depth diffscontractStatus()— perpetual contract statusrfqs()— public RFQ feedorders(options)— authenticated order updatesbalances(options)— authenticated balance updatespositions(options)— authenticated position reportsrfqDeliveries(options)— authenticated RFQ delivery events
The session is created lazily on the first stream request and reused for all subsequent ones.
Isolated sessions
Partial-depth snapshot streams (depth()) each get their own WsSession. This is because they connect to a different snapshot URL and their data semantics require a dedicated connection:
Code
When the stream is closed, its isolated session is torn down.
Order book session
orderBook() uses a separate shared session dedicated to order book management. All order book subscriptions multiplex over this one connection, but it is distinct from the main shared session:
Code
The book session handles snapshot synchronization, diff application, and automatic resync — see the Order Book deep dive for details.
Connection count summary
| Usage pattern | Connections |
|---|---|
| 3 public streams | 1 (shared) |
| 3 public + 2 authenticated streams | 1 (shared) |
| 2 order books | 1 (book) |
1 public stream + 1 depth() + 1 order book | 3 (shared + isolated + book) |
Reconnect behavior
When a WebSocket connection drops, WsTransport reconnects automatically with exponential backoff.
Backoff parameters
WebSocket reconnect backoff uses fixed defaults that are not configurable via client options (the backoff client option applies to HTTP retries only):
| Parameter | Value | Notes |
|---|---|---|
| Base delay | 250 ms | Fixed |
| Cap | 30 s | Fixed |
| Factor | 2 | Fixed |
| Jitter | Equal jitter | Half fixed + half random |
Attempt 0 reconnects immediately (delay = 0) — most drops are transient. Subsequent attempts grow exponentially: 250 ms → 500 ms → 1 s → ... capped at 30 s. Equal jitter prevents many clients from reconnecting in lockstep after an exchange restart.
What happens on reconnect
- The socket closes (unexpectedly — not by
close()) WsTransportschedules a reconnect after the backoff delay- If the session has authentication, fresh auth headers are generated via
headersFactory - A new socket is opened; stale sockets are ignored (late events from a superseded socket are discarded)
- On open, all durable subscriptions are replayed
- Streams transition:
"active"→"reconnecting"→"active"
Pending requests reject on reconnect
One-shot request/response methods (ping(), time(), conninfo(), listSubscriptions(), depthSnapshot()) that are in-flight when a reconnect occurs are rejected immediately. They are not replayed — you must retry them yourself:
Code
Mutations NEVER replay
Order placement, cancellation, and RFQ operations (placeOrder(), cancelOrder(), cancelAllOrders(), cancelSessionOrders(), rfq.submitQuote(), etc.) are classified as mutations. They are one-shot: rejected on disconnect, never retried or replayed. This is a safety guarantee — replaying a mutating request after reconnect could cause duplicate fills.
Subscription replay
Every SUBSCRIBE frame is stored by the transport. On reconnect, all stored subscriptions are re-sent to the fresh socket, restoring your streams without manual intervention.
Replay lifecycle
Code
Listen for replay events on individual streams:
Code
Stream states
A stream's .state property reflects its lifecycle:
| State | Meaning |
|---|---|
"active" | Connected and receiving data |
"reconnecting" | Connection dropped; waiting for reconnect and resubscription |
"failed" | An error occurred (check .lastError) |
"closed" | Explicitly closed by the caller |
Liveness checks
Long-lived WebSocket connections can go stale without a TCP close event (half-open connections, NAT timeouts, silent proxy drops). The SDK detects this with application-level ping/pong liveness checks.
Configuration
Pass webSocketLiveness when creating the client:
Code
How it works
- After each successful connection or ping response, a timer is scheduled at
intervalMs - When the timer fires, the session sends a
pingrequest over the WebSocket - If no response arrives within
timeoutMs, the connection is considered dead - The session forces a reconnect (triggering the normal reconnect/replay flow)
Without liveness configured, the SDK relies on the operating system's TCP keepalive and the exchange's server-side timeouts to detect dead connections.
Frame size limits
The SDK enforces a maximum inbound WebSocket message size to prevent memory exhaustion from malformed or malicious frames:
Code
Frames exceeding this limit are rejected: the SDK emits a ConnectionError, fires the error event on the transport, and closes the socket (which triggers a reconnect). The rejected frame is never delivered to stream listeners.
The size check uses UTF-8 byte length, not JavaScript string .length.
Diagnostic traffic classification
Every WebSocket diagnostic event is classified by traffic type for structured logging and observability:
| Classification | Events |
|---|---|
control | SUBSCRIBE, UNSUBSCRIBE, connection open/close, socket factory errors |
stream | Data frames (trades, depth updates, book tickers), malformed frames |
reconnect | Connection drop, reconnect scheduling, liveness failure |
mutation | Order placement/cancellation, RFQ operations |
Use the onDiagnostic callback to observe these:
Code
Close behavior
Closing the client
client.close() tears down all WebSocket sessions — shared, book, and isolated:
Code
This is immediate and suppresses reconnection. Pending requests reject with "WebSocket session closed".
Closing a stream
stream.close() sends an UNSUBSCRIBE frame and waits for the server's acknowledgement before resolving. The stream's resources are released, but the shared session stays open for other streams:
Code
If the unsubscribe times out, the stream is still cleaned up locally. Pass a timeout to control the wait:
Code
Closing an order book
book.close() unsubscribes from the book session and releases the book's resources:
Code
Listener cleanup with AbortSignal
Stream listeners support AbortSignal for automatic cleanup without manual off() calls:
Code
Related
- WebSocket Reference — wire-format tables and subscription details
- Order Book deep dive — order book synchronization internals
- Data Types deep dive — bigint and decimal string handling in WebSocket frames
- Authentication — how WebSocket connections authenticate