Skip to main content
Real-time order book data with support for both one-time snapshots and continuous subscription updates. Update interval: 100ms.

Query order book depth (one-time)

Fetch a single order book snapshot without subscribing to updates. params: [market, limit, price_interval]. Maximum limit: 100. Set price_interval to "0" for no grouping. Request Response

Subscribe to order book depth

Public depth updates exclude Retail Price Improvement (RPI) orders. The stream includes only regular order book liquidity.

Maintaining a local order book

To maintain an accurate local order book:
  1. Subscribe and wait for the first message (params[0] is true) — initialize order book from snapshot
  2. For each incremental update: if amount is "0" remove the price level; otherwise update or insert at the correct sorted position
  3. Keep asks sorted ascending, bids sorted descending
  4. Truncate to your desired limit after each update

Update model

First message (past_update_id absent): Full order book snapshot. Replace any existing local state. Subsequent messages (past_update_id present): Incremental deltas. Apply each price level change to the local book.

Ordering guarantees

  • Updates arrive in update_id order. Each incremental message’s past_update_id matches the previous message’s update_id.
  • If 10 seconds pass without activity, the server sends a full snapshot (params[0] is true, no past_update_id) as a keepalive. Treat this as a full reset.
  • A gap between past_update_id and the stored update_id indicates a missed message — re-subscribe to resync.

Order book data object

Code examples

type IDepth = [string, string];

interface OrderBook {
    asks: IDepth[];
    bids: IDepth[];
}

const ws = new WebSocket("wss://api.whitebit.com/ws");
const orderBook: OrderBook = { asks: [], bids: [] };
const LIMIT = 100;

ws.addEventListener("open", () => {
    ws.send(
        JSON.stringify({
            id: 1,
            method: "depth_subscribe",
            params: ["ETH_BTC", LIMIT, "0", true]
        }),
    );
});

ws.addEventListener("message", (event: MessageEvent) => {
    const message = JSON.parse(event.data.toString());

    if (message.method === "depth_update") {
        const updateData = message.params[0] as Partial<OrderBook & { past_update_id?: number }>;
        const isFirstMessage = !updateData.past_update_id;

        if (isFirstMessage) {
            // First message or keepalive snapshot is a full snapshot - replace order book
            orderBook.asks = updateData.asks ?? [];
            orderBook.bids = updateData.bids ?? [];
        } else {
            // Subsequent messages are incremental updates
            applyUpdates(orderBook.asks, updateData.asks, "ask");
            applyUpdates(orderBook.bids, updateData.bids, "bid");
            truncateOrderBook(orderBook.asks);
            truncateOrderBook(orderBook.bids);
        }
    }
});

function applyUpdates(orderBookSide: IDepth[], updates: IDepth[] | undefined, side: "ask" | "bid") {
    if (updates === undefined) return;
    for (const [price, amount] of updates) {
        const priceIndex = orderBookSide.findIndex((level) => level[0] === price);

        if (amount === "0") {
            if (priceIndex !== -1) {
                orderBookSide.splice(priceIndex, 1);
            }
        } else {
            if (priceIndex === -1) {
               const insertIndex = orderBookSide.findIndex((level) =>
                    side === "ask" ? level[0] > price : level[0] < price
                );

                if (insertIndex === -1) {
                    orderBookSide.push([price, amount]);
                } else {
                    orderBookSide.splice(insertIndex, 0, [price, amount]);
                }
            } else {
                orderBookSide[priceIndex][1] = amount;
            }
        }
    }
}

function truncateOrderBook(orderBookSide: IDepth[]) {
    if (orderBookSide.length > LIMIT) {
        orderBookSide.splice(LIMIT);
    }
}