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:- Subscribe and wait for the first message (
params[0]istrue) — initialize order book from snapshot - For each incremental update: if amount is
"0"remove the price level; otherwise update or insert at the correct sorted position - Keep asks sorted ascending, bids sorted descending
- 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_idorder. Each incremental message’spast_update_idmatches the previous message’supdate_id. - If 10 seconds pass without activity, the server sends a full snapshot (
params[0]istrue, nopast_update_id) as a keepalive. Treat this as a full reset. - A gap between
past_update_idand the storedupdate_idindicates 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);
}
}
import asyncio
import json
import websockets
class OrderBook:
def __init__(self):
self.asks = []
self.bids = []
LIMIT = 100
async def depth_subscribe():
async with websockets.connect('wss://api.whitebit.com/ws') as ws:
await ws.send(json.dumps({
'id': 1,
'method': 'depth_subscribe',
'params': ['ETH_BTC', LIMIT, '0', True]
}))
async for message in ws:
data = json.loads(message)
if data.get('method') == 'depth_update':
handle_depth_update(data['params'])
def handle_depth_update(params):
update_data = params[0]
is_first_message = update_data.get('past_update_id') is None
if is_first_message:
order_book.asks = update_data.get('asks', [])
order_book.bids = update_data.get('bids', [])
else:
apply_updates(order_book.asks, update_data.get('asks', []), "ask")
apply_updates(order_book.bids, update_data.get('bids', []), "bid")
truncate_order_book(order_book.asks)
truncate_order_book(order_book.bids)
def apply_updates(order_book_side, updates, side):
for price, amount in updates:
price = str(price)
amount = str(amount)
price_index = next((i for i, level in enumerate(order_book_side) if level[0] == price), -1)
if amount == '0':
if price_index != -1:
order_book_side.pop(price_index)
else:
if price_index == -1:
insert_index = next((i for i, level in enumerate(order_book_side)
if (side == "ask" and level[0] > price) or
(side == "bid" and level[0] < price)), -1)
if insert_index == -1:
order_book_side.append([price, amount])
else:
order_book_side.insert(insert_index, [price, amount])
else:
order_book_side[price_index][1] = amount
def truncate_order_book(order_book_side):
if len(order_book_side) > LIMIT:
del order_book_side[LIMIT:]
order_book = OrderBook()
asyncio.get_event_loop().run_until_complete(depth_subscribe())
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.json.*;
import javax.websocket.*;
@ClientEndpoint
public class OrderBookClient {
private static final int LIMIT = 100;
private static List<IDepth> asks = new ArrayList<>();
private static List<IDepth> bids = new ArrayList<>();
private static CountDownLatch latch;
public static void main(String[] args) throws URISyntaxException, InterruptedException {
latch = new CountDownLatch(1);
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
URI uri = new URI("wss://api.whitebit.com/ws");
container.connectToServer(OrderBookClient.class, uri);
latch.await(1, TimeUnit.MINUTES);
}
@OnOpen
public void onOpen(Session session) throws Exception {
JsonObject message = Json.createObjectBuilder()
.add("id", 1)
.add("method", "depth_subscribe")
.add("params", Json.createArrayBuilder()
.add("ETH_BTC")
.add(LIMIT)
.add("0")
.add(true)
)
.build();
session.getBasicRemote().sendText(message.toString());
}
@OnMessage
public void onMessage(String message, Session session) {
JsonReader reader = Json.createReader(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
JsonObject jsonMessage = reader.readObject();
if ("depth_update".equals(jsonMessage.getString("method"))) {
JsonArray params = jsonMessage.getJsonArray("params");
JsonObject updateData = params.getJsonObject(0);
boolean isFirstMessage = !updateData.containsKey("past_update_id") ||
updateData.isNull("past_update_id");
if (isFirstMessage) {
asks = parseOrderBookSide(updateData.getJsonArray("asks"));
bids = parseOrderBookSide(updateData.getJsonArray("bids"));
} else {
applyUpdates(asks, updateData.getJsonArray("asks"), "ask");
applyUpdates(bids, updateData.getJsonArray("bids"), "bid");
truncateOrderBook(asks);
truncateOrderBook(bids);
}
}
}
@OnClose
public void onClose(Session session, CloseReason reason) {
System.out.println("Connection closed: " + reason);
latch.countDown();
}
@OnError
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
latch.countDown();
}
private static List<IDepth> parseOrderBookSide(JsonArray jsonArray) {
List<IDepth> orderBookSide = new ArrayList<>();
for (JsonValue value : jsonArray) {
JsonArray level = value.asJsonArray();
orderBookSide.add(new IDepth(level.getString(0), level.getString(1)));
}
return orderBookSide;
}
private static void applyUpdates(List<IDepth> orderBookSide, JsonArray updates, String side) {
for (JsonValue value : updates) {
JsonArray level = value.asJsonArray();
String price = level.getString(0);
String amount = level.getString(1);
int priceIndex = -1;
for (int i = 0; i < orderBookSide.size(); i++) {
if (orderBookSide.get(i).getPrice().equals(price)) {
priceIndex = i;
break;
}
}
if ("0".equals(amount)) {
if (priceIndex != -1) orderBookSide.remove(priceIndex);
} else {
if (priceIndex == -1) {
int insertIndex = -1;
for (int i = 0; i < orderBookSide.size(); i++) {
if ((side.equals("ask") && orderBookSide.get(i).getPrice().compareTo(price) > 0) ||
(side.equals("bid") && orderBookSide.get(i).getPrice().compareTo(price) < 0)) {
insertIndex = i;
break;
}
}
if (insertIndex == -1) orderBookSide.add(new IDepth(price, amount));
else orderBookSide.add(insertIndex, new IDepth(price, amount));
} else {
orderBookSide.get(priceIndex).setAmount(amount);
}
}
}
}
private static void truncateOrderBook(List<IDepth> orderBookSide) {
if (orderBookSide.size() > LIMIT) orderBookSide.subList(LIMIT, orderBookSide.size()).clear();
}
static class IDepth {
private final String price;
private String amount;
public IDepth(String price, String amount) { this.price = price; this.amount = amount; }
public String getPrice() { return price; }
public String getAmount() { return amount; }
public void setAmount(String amount) { this.amount = amount; }
}
}