Order Book

Live order book depth over WebSocket — snapshot semantics, the levels/speed knobs, and the four rules that keep a bot from trading on a corrupt book.

Live order book depth (top-of-book snapshots) for every supported pair, streamed over socket.io. Companion to the Chart Price Stream — the connection and authentication model are the same; the payload semantics are not.

Connecting

PropertyValue
Protocolsocket.io v4 (not raw WebSocket)
Productionwss://bot.mywellat.com/bot/v1/orderbook
Stagingwss://pg-bot.mywellat.com/bot/v1/orderbook
Namespace/bot/v1/orderbook
Transportswebsocket only — disable HTTP long-polling

Authentication is identical to the Chart Price Stream — same credentials, same auth object (or x-bot-* header fallback), same 30-second window and failed-handshake block — except the signed path:

message = timestamp + "GET" + "/bot/v1/orderbook"
❗️

Sign each namespace separately

The path is part of the signed message, so a signature minted for /bot/v1/market will not authenticate here — it fails with AUTH_INVALID_SIGNATURE, and repeated attempts count toward the 15-minute auth block.

This is a separate connection from the chart price stream, and both count against the same 5-connections-per-key budget.

Subscribing

Send a subscribe event:

{ "pair": "BTCUSDT", "levels": 20, "speed": "1000ms" }
FieldRequiredValuesDefault
pairyesany pair from GET /bot/v1/spot/pairs
levelsno5, 10, 2020
speedno"1000ms", "100ms""1000ms"
❗️

There is no timeframe here

Unknown fields are rejected with SUB_UNEXPECTED_FIELD rather than ignored, so a copied chart-price payload fails loudly instead of appearing to work.

🚧

100ms is expensive — default to 1000ms

A 100ms stream delivers 10× the messages, counts as 5 subscriptions against your per-connection cap, and is separately capped at 2 fast streams per API key (SUB_SPEED_LIMIT_EXCEEDED beyond that). A bot that decides on closed candles has no use for 10 Hz depth.

To stop: send unsubscribe with { "pair": "BTCUSDT" } to drop every stream for the pair, or the full pair/levels/speed triple to drop one specific stream.

Events

EventPayloadMeaning
subscribed{ pair, levels, speed }Subscription active.
unsubscribed{ pair, levels, speed }Subscription removed.
orderBooksee belowA depth snapshot.
error{ status: false, code, message }Every failure — always listen to it.

The orderBook payload

{
  "event": "depth",
  "pair": "BTCUSDT",
  "levels": 20,
  "speed": "1000ms",
  "lastUpdateId": 69827431556,
  "bids": [
    ["64270.31000000", "0.48210000"],
    ["64270.30000000", "1.02900000"]
  ],
  "asks": [
    ["64270.32000000", "0.11500000"],
    ["64270.33000000", "2.40010000"]
  ]
}

Each entry is [price, quantity], both strings — parse with a decimal library, never Number, where the value feeds an order. bids are sorted descending and asks ascending, so bids[0] / asks[0] are the top of book.

Handling the book correctly

These four rules are where depth consumers go wrong:

❗️

Every message is a complete snapshot — replace your book, never merge

This is Binance partial depth, not the diff stream. Merging snapshots leaves phantom price levels in your book forever, and your bot sizes orders against liquidity nobody is quoting.

socket.on('orderBook', (msg) => {
  const prev = books.get(msg.pair);
  if (prev && msg.lastUpdateId <= prev.lastUpdateId) return; // stale or duplicate
  books.set(msg.pair, {
    // REPLACE — never merge
    bids: msg.bids,
    asks: msg.asks,
    lastUpdateId: msg.lastUpdateId,
  });
});
  • Drop stale snapshots. lastUpdateId increases monotonically; after a reconnect you may receive an older snapshot than the one you hold — discard anything with lastUpdateId <= your current one.
  • Reject a crossed book. If best bid ≥ best ask, the book is corrupt — discard it and wait for a clean snapshot. Never trade on it.
  • This is a truncated book. You see at most 20 levels per side — summing visible quantity tells you what's in the top 20 levels, not what the market can absorb. Size orders from the pair's filters (Trading Pairs), never from visible depth.
📘

The book is a reference, not an execution guarantee

At 1000ms it is up to a second stale on arrival, it reserves nothing, and every order is re-validated and re-priced at execution.

Reliability

  • Invalidate, don't coast. On disconnect, staleness, or a crossed book, discard the book and suspend depth-dependent decisions until a fresh snapshot arrives. A frozen order book actively lies about executable prices.
  • Tight staleness watchdog. A healthy stream emits on schedule regardless of trading activity — silence past ~5 seconds means dead, not quiet. Unsubscribe/re-subscribe, then force a reconnect if that fails. Do not reuse a chart-price watchdog threshold.
  • Subscriptions do not survive a reconnect — re-subscribe to everything on every connect, and clear held books first.
  • REST fallback: GET https://api.binance.com/api/v3/depth?symbol=BTCUSDT&limit=20 returns the same { lastUpdateId, bids, asks } shape. Poll no faster than the stream speed you replaced, and treat polling as explicitly degraded.

Errors

The shared codes (AUTH_*, SUB_INVALID_PAYLOAD, SUB_UNSUPPORTED_SYMBOL, SUB_LIMIT_EXCEEDED, RATE_LIMITED, UPSTREAM_UNAVAILABLE, INTERNAL) behave exactly as on the Chart Price Stream. Depth-specific additions:

CodeMeaningYour response
SUB_UNSUPPORTED_LEVELSlevels not in 5, 10, 20Fix client.
SUB_UNSUPPORTED_SPEEDspeed not in 1000ms, 100msFix client.
SUB_UNEXPECTED_FIELDUnknown field (typically a copied timeframe)Fix client.
SUB_SPEED_LIMIT_EXCEEDEDOver the per-key 100ms capUse 1000ms, or unsubscribe another fast stream.

Did this page help you?