Skip to main content
Real-time order book data with support for both one-time snapshots and continuous subscription updates. Each update contains changed price levels (asks and bids) as [price, amount] tuples. The server pushes incremental deltas every 100ms; when no changes occur for 10 seconds, a full snapshot is sent as a keepalive. Use this channel to maintain a live local order book for trading logic or display.
Connect to wss://api.whitebit.com/ws — see WebSocket Overview for protocol details and keepalive requirements.

Rate limits

Query order book depth (one-time)

Fetch a single order book snapshot without subscribing to updates. Request Response

Subscribe to order book depth

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

Including RPI orders in a local order book

Public depth excludes RPI orders by design, so they never appear in this stream. RPI resting liquidity is not published on any market-data feed — it is visible only in private active orders and in the exchange UI order book. To display an account’s own RPI orders alongside the public book, overlay its private active orders onto the local depth book. Subscribe to ordersPending (private) alongside depth_subscribe, then merge:
  1. Overlay only orders with rpi: true. Non-RPI orders already appear in the public depth aggregate — overlaying every order double-counts them.
  2. Aggregate by price before merging. Public depth levels are price-aggregated [price, total amount], while the private stream is per-order. Sum the RPI orders by price level, and use each order’s left (remaining amount), not amount (original size).
  3. Map the side. Order side 1 (sell) → ask; 2 (buy) → bid.
  4. Upsert by order_id. Add or update on event_id 1 (new) and 2 (update); remove on 3 (finish). order_id changes on modify — correlate with client_order_id. Stop-order activation emits a second event_id=1 for the same order_id, so upsert rather than append.
  5. Filter by market to match the depth subscription.
This overlay reflects only the account’s own RPI orders. No feed exposes other participants’ RPI liquidity.

Update frequency

The server pushes updates every 100ms when the order book changes. If 10 seconds pass without changes, a full snapshot is sent as a keepalive.

Error codes

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 the configured 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);
    }
}