Skip to content

Orderbook Updates

Note: The subscription type is "orderbook" (lowercase). Some exchanges use orderBook (camelCase) for similar functionality.

Receive live orderbook depth updates for specific trading pairs. Subscriptions support two delivery formats: diff (incremental changes, the default) and snapshot (full book on every update).

Endpoint

Public subscriptions (candles, marketPrices, orderbook, trades):

wss://papi.synthetix.io/v1/ws/info

Private subscriptions (subAccountUpdates - requires authentication):

wss://papi.synthetix.io/v1/ws/trade

Subscription Request

Orderbook updates are public data and do not require authentication.

Subscribe (Diff Mode — Default)

{
  "id": "sub-1",
  "method": "subscribe",
  "params": {
    "type": "orderbook",
    "symbol": "BTC-USDT"
  }
}

Equivalent to format: "diff", depth: 50, updateFrequencyMs: 250.

Subscribe (Snapshot Mode)

{
  "id": "sub-1",
  "method": "subscribe",
  "params": {
    "type": "orderbook",
    "symbol": "BTC-USDT",
    "format": "snapshot"
  }
}

Subscribe with Custom Parameters

{
  "id": "sub-1",
  "method": "subscribe",
  "params": {
    "type": "orderbook",
    "symbol": "BTC-USDT",
    "format": "diff",
    "depth": 100,
    "updateFrequencyMs": 500
  }
}

Request Parameters

ParameterTypeRequiredDescription
idstringYesClient-generated unique request identifier
methodstringYesMust be "subscribe"
params.typestringYesMust be "orderbook"
params.symbolstringYesTrading pair symbol (e.g., "BTC-USDT"). Must be a specific symbol — "ALL" is not supported.
params.formatstringNo"diff" (default) or "snapshot"
params.depthintegerNoNumber of price levels per side: 10, 50 (default), or 100
params.updateFrequencyMsintegerNoUpdate interval in milliseconds (default: 250). See allowed values below.

Subscribe Response

A successful subscribe response echoes the negotiated parameters:

{
  "id": "sub-1",
  "requestId": "sub-1",
  "status": 200,
  "result": {
    "type": "orderbook",
    "symbol": "BTC-USDT",
    "format": "diff",
    "depth": 50,
    "updateFrequencyMs": 250,
    "seq": 987654321
  }
}

The response echoes both id and requestId for backwards compatibility. The returned seq is the matching-engine sequence at subscription time. The first notification will have meseq >= this value.

Update Modes

Diff Mode (Default)

In diff mode, you receive:

  1. An initial snapshot (type: "snapshot") containing the full orderbook at your subscribed depth
  2. Incremental diffs (type: "diff") containing only the price levels that changed since the last update

Diff mode significantly reduces bandwidth but requires you to maintain local orderbook state and apply updates. You must also validate continuity using the meseq/prevMeseq fields and verify integrity with the checksum.

Snapshot Mode

In snapshot mode, every message is a full orderbook snapshot at your subscribed depth. This is simpler to implement — just replace your local state with each message — but uses more bandwidth.

Message Format

Initial Snapshot (Diff Mode)

When subscribing with format: "diff", the first message is a full snapshot with type: "snapshot". This establishes the baseline for subsequent diffs:

{
  "channel": "orderbookUpdate",
  "method": "orderbook_depth_update",
  "type": "snapshot",
  "meseq": 987654321,
  "met": 1704067200123456,
  "prevMeseq": null,
  "checksum": "a1b2c3d4",
  "data": {
    "symbol": "BTC-USDT",
    "timestamp": "2026-01-07T17:08:29Z",
    "bids": [
      {"price": "100000.00", "quantity": "1.5"},
      {"price": "99950.00", "quantity": "2.0"}
    ],
    "asks": [
      {"price": "100050.00", "quantity": "1.2"},
      {"price": "100100.00", "quantity": "1.8"}
    ]
  },
  "timestamp": 1704067200000
}

method is deprecated. Use channel to identify notification type. See Message Fields.

Diff Message

Sent for subsequent updates in diff mode. Contains only the price levels that changed:

{
  "channel": "orderbookUpdate",
  "method": "orderbook_depth_update",
  "type": "diff",
  "meseq": 987654322,
  "met": 1704067201123456,
  "prevMeseq": 987654321,
  "checksum": "b2c3d4e5",
  "data": {
    "symbol": "BTC-USDT",
    "timestamp": "2026-01-07T17:08:30Z",
    "bids": [
      {"price": "100000.00", "quantity": "1.2"},
      {"price": "99900.00", "quantity": "0"}
    ],
    "asks": [
      {"price": "100150.00", "quantity": "0.5"}
    ]
  },
  "timestamp": 1704067200100
}

method is deprecated. Use channel to identify notification type. See Message Fields.

In this example:

  • Bid at 100000.00 had its quantity updated from 1.5 to 1.2
  • Bid at 99900.00 was removed (quantity: "0")
  • A new ask level appeared at 100150.00
  • All other levels remain unchanged

Snapshot Mode Message

When subscribing with format: "snapshot", every message is a full orderbook. In snapshot mode the type field is omitted:

{
  "channel": "orderbookUpdate",
  "method": "orderbook_depth_update",
  "meseq": 987654321,
  "met": 1704067200123456,
  "checksum": "a1b2c3d4",
  "data": {
    "symbol": "BTC-USDT",
    "timestamp": "2026-01-07T17:08:29Z",
    "bids": [
      {"price": "100000.00", "quantity": "1.5"},
      {"price": "99950.00", "quantity": "2.0"}
    ],
    "asks": [
      {"price": "100050.00", "quantity": "1.2"},
      {"price": "100100.00", "quantity": "1.8"}
    ]
  },
  "timestamp": 1704067200000
}

method is deprecated. Use channel to identify notification type. See Message Fields.

Message Fields

FieldTypeDescription
channelstring"orderbookUpdate" — the canonical channel identifier
methodstring"orderbook_depth_update" (deprecated) — legacy identifier, included on all notifications for backwards compatibility. Use channel instead.
typestring"snapshot" for full book, "diff" for incremental changes. Omitted in snapshot-mode subscriptions.
meseqintegerMatching-engine sequence watermark for this update
metintegerMatching-engine event time in Unix microseconds
prevMeseqinteger | nullExpected previous matching-engine sequence. null on snapshots.
checksumstringCRC32 checksum of the full depth-truncated orderbook state after applying this message (see Checksum Validation)
data.symbolstringTrading pair symbol
data.timestampstringRFC 3339 timestamp of the orderbook state (e.g., "2026-01-07T17:08:29Z")
data.bidsarrayBid price levels, sorted by price descending (highest first)
data.asksarrayAsk price levels, sorted by price ascending (lowest first)
timestampintegerServer send time in Unix milliseconds

Diff Semantics

In a diff message, each price level in bids and asks represents a change:

ScenarioRepresentation
New levelLevel appears in the diff with a non-zero quantity
Updated levelLevel appears with the new quantity (replaces the previous quantity at that price)
Removed levelLevel appears with quantity: "0"

Levels not present in a diff message are unchanged.

Price Level Format

Each price level is an object with price and quantity fields:

{"price": "100000.50", "quantity": "1.25"}

Where:

  • price (string): Price level
  • quantity (string): Total quantity at this level. "0" in a diff means the level was removed.

Depth

The subscription depth parameter controls how many price levels per side are included. Available values:

DepthDescription
10Top 10 price levels per side
50Top 50 price levels per side (default)
100Top 100 price levels per side

For deeper snapshots, use the Get Orderbook request/response endpoint which supports depths up to 1000 levels.

Update Frequency

The updateFrequencyMs parameter controls the minimum interval between updates. The server batches changes within each interval and delivers a single message per tick.

Allowed values: 50, 100, 250 (default), 500, 1000.

Constraint: When depth=100, only updateFrequencyMs values of 250, 500, or 1000 are supported.

Managing Local Orderbook

Diff Mode

When using diff mode, you must maintain local orderbook state and apply each diff as it arrives.

class OrderbookManager {
  constructor() {
    this.orderbooks = new Map(); // symbol → { bids: Map<price, quantity>, asks: Map<price, quantity>, meseq }
  }
 
  processMessage(message) {
    const { type, meseq, prevMeseq, checksum, data } = message;
    const { symbol } = data;
 
    if (type === "snapshot") {
      // Replace entire local state
      this.orderbooks.set(symbol, {
        bids: new Map(data.bids.map(l => [l.price, l.quantity])),
        asks: new Map(data.asks.map(l => [l.price, l.quantity])),
        meseq
      });
      return;
    }
 
    // type === "diff"
    const book = this.orderbooks.get(symbol);
    if (!book) return; // No baseline — wait for snapshot
 
    // 1. Verify continuity using prevMeseq
    if (prevMeseq !== book.meseq) {
      // Gap or out-of-order — local state may be stale
      console.error(`Sequence gap for ${symbol}: expected prevMeseq=${book.meseq}, got ${prevMeseq}`);
      this.orderbooks.delete(symbol);
      this.resubscribe(symbol);
      return;
    }
 
    // 2. Apply changes
    for (const level of data.bids) {
      if (level.quantity === "0") {
        book.bids.delete(level.price);
      } else {
        book.bids.set(level.price, level.quantity);
      }
    }
    for (const level of data.asks) {
      if (level.quantity === "0") {
        book.asks.delete(level.price);
      } else {
        book.asks.set(level.price, level.quantity);
      }
    }
    book.meseq = meseq;
 
    // 3. Verify checksum
    if (!this.verifyChecksum(symbol, checksum)) {
      console.error(`Checksum mismatch for ${symbol} — resubscribing`);
      this.orderbooks.delete(symbol);
      this.resubscribe(symbol);
    }
  }
 
  getSortedBook(symbol) {
    const book = this.orderbooks.get(symbol);
    if (!book) return null;
    return {
      bids: [...book.bids.entries()]
        .sort((a, b) => parseFloat(b[0]) - parseFloat(a[0]))
        .map(([price, quantity]) => ({ price, quantity })),
      asks: [...book.asks.entries()]
        .sort((a, b) => parseFloat(a[0]) - parseFloat(b[0]))
        .map(([price, quantity]) => ({ price, quantity }))
    };
  }
}

Snapshot Mode

In snapshot mode, each message is the complete orderbook — simply replace local state:

class SnapshotOrderbookManager {
  constructor() {
    this.orderbooks = new Map();
  }
 
  processMessage(message) {
    const { data } = message;
    this.orderbooks.set(data.symbol, {
      bids: data.bids,
      asks: data.asks,
      timestamp: data.timestamp
    });
  }
}

Usage Example

const ws = new WebSocket('wss://papi.synthetix.io/v1/ws/info');
const orderbookManager = new OrderbookManager();
 
ws.onopen = () => {
  ws.send(JSON.stringify({
    id: "sub-1",
    method: "subscribe",
    params: {
      type: "orderbook",
      symbol: "BTC-USDT"
    }
  }));
};
 
ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
 
  if (message.channel === "orderbookUpdate") {
    orderbookManager.processMessage(message);
 
    const book = orderbookManager.getSortedBook("BTC-USDT");
    if (book && book.bids.length && book.asks.length) {
      console.log(`Best bid: ${book.bids[0].price} / Best ask: ${book.asks[0].price}`);
    }
  }
};

Checksum Validation

The checksum field contains a CRC32 (IEEE) checksum of the full depth-truncated orderbook state after applying the message. Use it to verify that your local state matches the server's.

Checksum Algorithm

  1. Start with the depth-truncated orderbook after applying the update (sorted bids descending, asks ascending)
  2. Build an input string by concatenating all levels:
    • For each bid: b<price>:<quantity>|
    • For each ask: a<price>:<quantity>|
    • Bids come first, then asks
  3. Compute CRC32 (IEEE) of the resulting byte string
  4. Format as an 8-character lowercase hexadecimal string
Example input string:
b100000.00:1.5|b99950.00:2.0|a100050.00:1.2|a100100.00:1.8|

JavaScript Implementation

import crc32 from 'crc-32'; // npm install crc-32
 
function computeChecksum(bids, asks) {
  let input = '';
  for (const [price, quantity] of bids) {
    input += `b${price}:${quantity}|`;
  }
  for (const [price, quantity] of asks) {
    input += `a${price}:${quantity}|`;
  }
  const value = crc32.buf(Buffer.from(input)) >>> 0; // unsigned
  return value.toString(16).padStart(8, '0');
}

Important: The checksum is computed over the depth-truncated book at your subscribed depth. If your local book has accumulated more levels than your subscribed depth (e.g., from levels moving in and out of range), truncate to the top N levels per side before computing.

Recovery

Diff Mode Recovery

If any of the following occur, discard your local orderbook and resubscribe:

  • Continuity break: A diff message's prevMeseq does not match your last stored meseq, indicating a gap or out-of-order delivery
  • Checksum mismatch: Your locally computed checksum does not match the message checksum
  • Missing baseline: You receive a diff but have no local snapshot

Resubscribing gives you a fresh snapshot to re-establish your baseline:

function resubscribe(ws, symbol) {
  ws.send(JSON.stringify({
    id: "resub-" + Date.now(),
    method: "subscribe",
    params: {
      type: "orderbook",
      symbol
    }
  }));
}

Fetching a Snapshot Manually

You can also fetch a one-time snapshot via request/response to sync your state without resubscribing:

Snapshot Mode Recovery

In snapshot mode, reconnection is simple — each message is a full book, so just reconnect and resubscribe.

Performance Optimization

Backpressure: Slow Consumer Disconnection

If your client cannot consume messages fast enough, the server will silently remove your subscription. The WebSocket send buffer has a fixed capacity, and when it fills up, the server drops the subscriber rather than blocking other clients. You will not receive an error message — the orderbook updates will simply stop arriving.

To avoid this:

  • Process incoming messages promptly (avoid blocking the WebSocket message handler)
  • Choose an appropriate updateFrequencyMs for your use case — slower frequencies produce fewer messages
  • Monitor for gaps in updates and resubscribe if needed

Choosing Parameters

Use CaseRecommended Settings
HFT / market makingdepth: 10, updateFrequencyMs: 50, format: "diff"
Trading UIdepth: 50, updateFrequencyMs: 250, format: "diff"
Analytics / monitoringdepth: 50, updateFrequencyMs: 500, format: "snapshot"
Simple integrationdepth: 10, format: "snapshot"

Authentication

Orderbook updates are public data and do not require authentication. They are available on the public /info endpoint.

Common Issues

IssueDescriptionSolution
Connection lostWebSocket disconnectionReconnect and resubscribe
Updates stoppedSlow consumer — server removed subscriptionChoose a slower updateFrequencyMs, process messages faster, and resubscribe
Checksum mismatchLocal state has diverged from serverDiscard local state and resubscribe
Invalid symbolSymbol not availableCheck supported markets
"ALL" rejectedWildcard subscriptions not supportedSubscribe to each symbol individually

Implementation Notes

  • Default is diff mode: Subscriptions default to format: "diff" — you must maintain local state and apply updates
  • Configurable depth: Choose 10, 50, or 100 levels per side
  • Timer-driven delivery: Updates are batched and sent at the configured updateFrequencyMs interval
  • Checksum on every message: Both snapshot and diff messages include a CRC32 checksum for validation
  • No "ALL" symbol: You must subscribe to each symbol individually
  • Snapshot fallback: In diff mode, if changes are very large the server may send a snapshot instead of a diff — always check the type field

Related Endpoints