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 — Order Book Reconstruction

client.orderBook(symbol) returns a LiveOrderBook — a local L2 order book the SDK keeps synchronized from the WebSocket depth stream. This page explains how it stays correct, how it detects and recovers from gaps, and the guarantees it makes.

The snapshot + diff model

The exchange does not stream the full book on every change. Instead:

  1. The SDK subscribes to the depth stream and requests an initial snapshot — the complete book at a point in time.
  2. Every subsequent message is a diff (DepthUpdate) — only the price levels that changed.
  3. The SDK applies each diff in sequence to keep its local copy current.

The first "update" event you receive carries the full book (the snapshot). Every event after that carries only the changed levels.

Code
const book = client.orderBook("BTCUSD"); book.on("update", (lob, delta) => { // `delta` = only the levels that changed on this update // `lob` = the full current book, queried on demand console.log(lob.bestBid(), lob.bestAsk()); });

Sequence integrity

Each diff carries two sequence IDs: U (first update ID) and u (last update ID). The SDK tracks the last applied u and enforces strict ordering:

  • Stale diff (u <= lastUpdateId): already covered, silently dropped.
  • Contiguous diff (U <= lastUpdateId < u): applied, lastUpdateId advances to u.
  • Gap (U > lastUpdateId): a frame was missed. The book can no longer be trusted — it raises ResyncRequiredError internally and goes stale.

Gemini's depth stream overlaps at U == lastUpdateId (unlike a strictly contiguous stream), so any U strictly greater than the last applied ID indicates a real gap, not normal overlap.

Self-healing

When a gap is detected — or a malformed frame arrives, or the connection reconnects — the book:

  1. Marks itself stale (live = false).
  2. Emits a single "resync" event (deduplicated: one per stale period).
  3. Requests a fresh snapshot in the background.
  4. On snapshot arrival, rebuilds and emits a full-book "update".

You never call anything to trigger recovery — it is automatic. Your job is to respect the "resync" signal:

Code
book.on("resync", () => { // The book is stale and rebuilding. Do not trade on it until // the next "update" arrives with the fresh snapshot. console.warn("Order book resyncing — treat current state as unavailable"); });

Stale reads return nothing

While stale, every read method returns empty — a gapped book must never look tradeable:

MethodWhile liveWhile stale
bestBid() / bestAsk()Levelundefined
topN(side, n)Level[][]
spread() / mid()numberundefined
snapshot(){ bids, asks }{ bids: [], asks: [] }

This means you cannot accidentally read a torn book: if bestBid() returns undefined, the book is either not yet initialized or currently resyncing.

Reads

Code
book.bestBid(); // { price, qty } | undefined — highest bid book.bestAsk(); // { price, qty } | undefined — lowest ask book.topN("bids", 10); // Level[] — top 10 bids, best-first book.spread(); // number | undefined — ask − bid (display only) book.mid(); // number | undefined — midpoint (display only) book.snapshot(); // { bids: Level[], asks: Level[] } — full book copy

spread() and mid() return floating-point numbers for display. Do not use them for exact execution math — prices on the wire are decimal strings (see Data Types) and converting to float loses precision.

Price level identity

Levels are keyed by a canonical price string, so "0.50" and "0.5" map to the same level. This guarantees a removal (quantity: "0") can never leave a stale duplicate at a differently-formatted price.

Separate session

The order book runs on its own WebSocket session, isolated from public streams (trades, bookTicker, etc.). A reconnect or failure on one does not stall the other. In sandbox, the SDK uses a dedicated snapshot stream automatically.

Events

Code
book.on("update", (lob, delta) => { /* book changed */ }); book.on("resync", () => { /* stale, rebuilding — protect yourself */ }); book.on("error", (err) => { /* SdkError — malformed frame, etc. */ }); // Auto-remove on abort: const controller = new AbortController(); book.on("update", handler, { signal: controller.signal }); controller.abort(); book.close(); // stop updates, remove listeners, release the stream

Errors are always delivered as SdkError. A malformed depth frame (e.g. a level that isn't a [price, quantity] string tuple) is wrapped so the "error" listener always receives an SdkError, and the book goes stale before the listener runs — a throwing listener cannot bypass recovery.

What's next

  • WebSocket Reference — the DepthUpdate wire format
  • Data Types — decimal strings and precision
Last modified on August 14, 2026
WebSocketOrder Book Reconstruction
On this page
  • The snapshot + diff model
  • Sequence integrity
  • Self-healing
  • Stale reads return nothing
  • Reads
  • Price level identity
  • Separate session
  • Events
  • What's next
TypeScript
TypeScript
TypeScript
TypeScript