GeminiGemini
SandboxGet API key
  • Crypto Trading
  • Prediction Markets
  • SDKs & Tools
Changelog
Gemini Crypto Exchange LogoGemini Crypto Exchange Logo

© 2026 Gemini Space Station, Inc.

Overview
TypeScript SDK
    QuickstartAuthenticationWebSocketError HandlingPatterns & Recipes
    API Reference
    Deep Dives
      Order Book ReconstructionRFQ ProtocolData TypesRequest ValidationWebSocket SessionsTransport & Signing
Deep Dives

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 stream
  • bookTicker(symbol) — public best bid/ask
  • depthUpdates(symbol) — public depth diffs
  • contractStatus() — perpetual contract status
  • rfqs() — public RFQ feed
  • orders(options) — authenticated order updates
  • balances(options) — authenticated balance updates
  • positions(options) — authenticated position reports
  • rfqDeliveries(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
// Each call creates a separate WebSocket connection const depth5 = client.websocket.depth("BTCUSD", { levels: 5 }); const depth20 = client.websocket.depth("ETHUSD", { levels: 20 }); // That's 2 separate WebSocket connections, plus the shared session

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
const btcBook = client.websocket.orderBook("BTCUSD"); const ethBook = client.websocket.orderBook("ETHUSD"); // Both share the same book session (1 connection), separate from the main session

The book session handles snapshot synchronization, diff application, and automatic resync — see the Order Book deep dive for details.

Connection count summary

Usage patternConnections
3 public streams1 (shared)
3 public + 2 authenticated streams1 (shared)
2 order books1 (book)
1 public stream + 1 depth() + 1 order book3 (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):

ParameterValueNotes
Base delay250 msFixed
Cap30 sFixed
Factor2Fixed
JitterEqual jitterHalf 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

  1. The socket closes (unexpectedly — not by close())
  2. WsTransport schedules a reconnect after the backoff delay
  3. If the session has authentication, fresh auth headers are generated via headersFactory
  4. A new socket is opened; stale sockets are ignored (late events from a superseded socket are discarded)
  5. On open, all durable subscriptions are replayed
  6. 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
try { const pong = await client.websocket.ping(); } catch (err) { // If the connection dropped mid-request, this rejects with: // "WebSocket session reconnecting" }

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
socket closes → backoff delay → new socket opens → replay all subscriptions → await ACK for each → "resubscribed" event fires per stream

Listen for replay events on individual streams:

Code
const trades = client.websocket.trades("BTCUSD"); trades.on("resubscribed", () => { console.log("Subscription restored after reconnect"); }); trades.on("subscriptionError", (err) => { console.error("Resubscription failed:", err); // The stream state is now "failed" });

Stream states

A stream's .state property reflects its lifecycle:

StateMeaning
"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
import { createClient, HmacAuth } from "gemini-markets/server"; const client = await createClient({ // ... webSocketLiveness: { intervalMs: 30_000, // ping every 30 seconds (default) timeoutMs: 10_000, // expect pong within 10 seconds (default) }, });

How it works

  1. After each successful connection or ping response, a timer is scheduled at intervalMs
  2. When the timer fires, the session sends a ping request over the WebSocket
  3. If no response arrives within timeoutMs, the connection is considered dead
  4. 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
const client = await createClient({ // ... webSocketMaxMessageSizeBytes: 1_048_576, // 1 MB (default) });

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:

ClassificationEvents
controlSUBSCRIBE, UNSUBSCRIBE, connection open/close, socket factory errors
streamData frames (trades, depth updates, book tickers), malformed frames
reconnectConnection drop, reconnect scheduling, liveness failure
mutationOrder placement/cancellation, RFQ operations

Use the onDiagnostic callback to observe these:

Code
const client = await createClient({ // ... onDiagnostic: (event) => { if (event.traffic === "reconnect") { metrics.increment("ws.reconnect"); } }, });

Close behavior

Closing the client

client.close() tears down all WebSocket sessions — shared, book, and isolated:

Code
client.close(); // All sessions closed, all pending requests rejected, all streams emit "close"

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
const trades = client.websocket.trades("BTCUSD"); await trades.ready; // wait for subscription ACK // Later... await trades.close(); // sends UNSUBSCRIBE, waits for ACK, then resolves

If the unsubscribe times out, the stream is still cleaned up locally. Pass a timeout to control the wait:

Code
await trades.close({ timeoutMs: 5_000 });

Closing an order book

book.close() unsubscribes from the book session and releases the book's resources:

Code
const book = client.websocket.orderBook("BTCUSD"); // ... book.close(); // unsubscribes, releases memory

Listener cleanup with AbortSignal

Stream listeners support AbortSignal for automatic cleanup without manual off() calls:

Code
const controller = new AbortController(); trades.on("message", (trade) => { console.log(trade.p, trade.q); }, { signal: controller.signal }); // Later: remove the listener without a reference to the callback controller.abort();

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
Last modified on August 14, 2026
Request ValidationTransport & Signing
On this page
  • Session architecture
    • Shared session
    • Isolated sessions
    • Order book session
    • Connection count summary
  • Reconnect behavior
    • Backoff parameters
    • What happens on reconnect
    • Pending requests reject on reconnect
    • Mutations NEVER replay
  • Subscription replay
    • Replay lifecycle
    • Stream states
  • Liveness checks
    • Configuration
    • How it works
  • Frame size limits
  • Diagnostic traffic classification
  • Close behavior
    • Closing the client
    • Closing a stream
    • Closing an order book
    • Listener cleanup with AbortSignal
  • Related
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript
TypeScript