Orderbook Updates
Note: The subscription type is "orderbook" (lowercase). Some exchanges use orderBook (camelCase) for similar functionality.
Receive live changes to market depth, price levels, and liquidity for specific trading pairs.
Endpoint
Public subscriptions (candles, marketPrices, orderbook):
wss://papi.synthetix.io/v1/ws/infoPrivate subscriptions (subAccountUpdates - requires authentication):
wss://papi.synthetix.io/v1/ws/tradeSubscription Request
Orderbook updates are public data and do not require authentication.
Subscribe to Single Market
{
"id": "sub-1",
"method": "subscribe",
"params": {
"type": "orderbook",
"symbol": "BTC-USDT"
}
}Subscribe to All Markets
{
"id": "sub-all",
"method": "subscribe",
"params": {
"type": "orderbook",
"symbol": "ALL"
}
}Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-generated unique request identifier |
method | string | Yes | Must be "subscribe" |
params.type | string | Yes | Must be "orderbook" |
params.symbol | string | Yes | Trading pair symbol (e.g., "BTC-USDT") or "ALL" for all markets |
Orderbook Update Messages
Initial Snapshot
Upon subscription, you'll receive a full orderbook snapshot:
{
"method": "orderbook_depth_update",
"data": {
"symbol": "BTC-USDT",
"timestamp": "2025-01-01T00:00:00.000Z",
"bids": [
{"price": "50000.00", "quantity": "1.5"},
{"price": "49999.50", "quantity": "2.0"},
{"price": "49999.00", "quantity": "0.8"}
],
"asks": [
{"price": "50001.00", "quantity": "1.2"},
{"price": "50001.50", "quantity": "3.1"},
{"price": "50002.00", "quantity": "0.9"}
]
}
}Incremental Updates
After the snapshot, you'll receive incremental updates with the same structure:
{
"method": "orderbook_depth_update",
"data": {
"symbol": "BTC-USDT",
"timestamp": "2025-01-01T00:00:01.000Z",
"bids": [
{"price": "50000.00", "quantity": "0.0"}, // Removed (quantity = 0)
{"price": "49998.50", "quantity": "1.0"} // Added/Updated
],
"asks": [
{"price": "50002.00", "quantity": "1.5"}, // Updated quantity
{"price": "50003.00", "quantity": "0.5"} // Added new level
]
}
}Price Level Format
Each price level is represented as an object with price and quantity fields:
{"price": "50000.50", "quantity": "1.25"}Where:
price(string): Price levelquantity(string): Total quantity at this level
Special Case: When quantity is "0.0", the price level should be removed from the orderbook.
Managing Local Orderbook
Initial Setup
class OrderbookManager {
constructor() {
this.orderbooks = new Map();
}
processOrderbookUpdate(data) {
const { symbol } = data;
// If orderbook doesn't exist, initialize it
if (!this.orderbooks.has(symbol)) {
this.initializeOrderbook(symbol, data);
} else {
this.updateOrderbook(symbol, data);
}
}
initializeOrderbook(symbol, data) {
const orderbook = {
symbol,
bids: new Map(),
asks: new Map(),
timestamp: data.timestamp
};
// Load initial bids
data.bids.forEach(({price, quantity}) => {
orderbook.bids.set(price, quantity);
});
// Load initial asks
data.asks.forEach(({price, quantity}) => {
orderbook.asks.set(price, quantity);
});
this.orderbooks.set(symbol, orderbook);
console.log(`Orderbook initialized for ${symbol}`);
}
updateOrderbook(symbol, data) {
const orderbook = this.orderbooks.get(symbol);
if (!orderbook) {
console.error(`No orderbook found for ${symbol}`);
return;
}
// Apply bid updates
data.bids?.forEach(({price, quantity}) => {
if (quantity === "0.0") {
orderbook.bids.delete(price);
} else {
orderbook.bids.set(price, quantity);
}
});
// Apply ask updates
data.asks?.forEach(({price, quantity}) => {
if (quantity === "0.0") {
orderbook.asks.delete(price);
} else {
orderbook.asks.set(price, quantity);
}
});
orderbook.timestamp = data.timestamp;
this.notifyOrderbookChange(symbol, orderbook);
}
getOrderbook(symbol) {
return this.orderbooks.get(symbol);
}
getBestBidAsk(symbol) {
const orderbook = this.orderbooks.get(symbol);
if (!orderbook) return null;
const bestBid = Math.max(...Array.from(orderbook.bids.keys()).map(parseFloat));
const bestAsk = Math.min(...Array.from(orderbook.asks.keys()).map(parseFloat));
return {
bestBid: bestBid.toString(),
bestAsk: bestAsk.toString(),
spread: (bestAsk - bestBid).toString(),
spreadPercent: ((bestAsk - bestBid) / bestBid * 100).toFixed(4)
};
}
getDepth(symbol, levels = 10) {
const orderbook = this.orderbooks.get(symbol);
if (!orderbook) return null;
// Sort and limit bids (highest first)
const bids = Array.from(orderbook.bids.entries())
.sort(([a], [b]) => parseFloat(b) - parseFloat(a))
.slice(0, levels);
// Sort and limit asks (lowest first)
const asks = Array.from(orderbook.asks.entries())
.sort(([a], [b]) => parseFloat(a) - parseFloat(b))
.slice(0, levels);
return { bids, asks };
}
notifyOrderbookChange(symbol, orderbook) {
// Emit events for UI updates
this.emit('orderbookChange', {
symbol,
orderbook,
bestBidAsk: this.getBestBidAsk(symbol),
timestamp: Date.now()
});
}
}Usage Example
const ws = new WebSocket('wss://papi.synthetix.io/v1/ws/info');
const orderbookManager = new OrderbookManager();
ws.onopen = () => {
// Subscribe to orderbook updates
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.method === "orderbook_depth_update") {
orderbookManager.processOrderbookUpdate(message.data);
}
};
// Listen for orderbook changes
orderbookManager.on('orderbookChange', (event) => {
const { symbol, bestBidAsk } = event;
console.log(`${symbol} Best: ${bestBidAsk.bestBid}/${bestBidAsk.bestAsk}`);
// Update UI
updateOrderbookDisplay(symbol, orderbookManager.getDepth(symbol, 20));
});Performance Optimization
Efficient Data Structures
// Use optimized data structure for large orderbooks
class OptimizedOrderbook {
constructor() {
this.bids = new Map(); // Could use sorted tree for very large books
this.asks = new Map();
this.bidsSorted = []; // Cache sorted arrays
this.asksSorted = [];
this.sortedDirty = true;
}
updateLevel(side, price, quantity) {
const book = side === 'bid' ? this.bids : this.asks;
if (quantity === "0.0") {
book.delete(price);
} else {
book.set(price, quantity);
}
this.sortedDirty = true;
}
getSortedLevels(side, limit = 10) {
if (this.sortedDirty) {
this.updateSortedCache();
}
const sorted = side === 'bid' ? this.bidsSorted : this.asksSorted;
return sorted.slice(0, limit);
}
updateSortedCache() {
this.bidsSorted = Array.from(this.bids.entries())
.sort(([a], [b]) => parseFloat(b) - parseFloat(a));
this.asksSorted = Array.from(this.asks.entries())
.sort(([a], [b]) => parseFloat(a) - parseFloat(b));
this.sortedDirty = false;
}
}Rate Limiting Updates
class ThrottledOrderbookManager extends OrderbookManager {
constructor(throttleMs = 50) {
super();
this.throttleMs = throttleMs;
this.pendingUpdates = new Map();
this.updateTimers = new Map();
}
updateOrderbook(symbol, data) {
// Store latest update
this.pendingUpdates.set(symbol, data);
// Clear existing timer
if (this.updateTimers.has(symbol)) {
clearTimeout(this.updateTimers.get(symbol));
}
// Set new timer
const timer = setTimeout(() => {
const latestUpdate = this.pendingUpdates.get(symbol);
if (latestUpdate) {
super.updateOrderbook(symbol, latestUpdate);
this.pendingUpdates.delete(symbol);
this.updateTimers.delete(symbol);
}
}, this.throttleMs);
this.updateTimers.set(symbol, timer);
}
}Authentication
Orderbook updates are public data and do not require authentication. They are available on the public /info endpoint without any authentication.
Error Handling
Connection Recovery
handleConnectionError(symbol) {
const orderbook = this.orderbooks.get(symbol);
if (!orderbook) {
console.error(`No orderbook for ${symbol}, requesting snapshot`);
this.resubscribe(symbol);
return;
}
// Clear existing orderbook and resubscribe
this.orderbooks.delete(symbol);
this.resubscribe(symbol);
}
resubscribe(symbol) {
// Re-subscribe to get fresh snapshot
const subscription = {
id: "resub-" + Date.now(),
method: "subscribe",
params: {
type: "orderbook",
symbol
}
};
this.ws.send(JSON.stringify(subscription));
}Common Issues
| Error | Description | Solution |
|---|---|---|
| Connection lost | WebSocket disconnection | Reconnect and resubscribe |
| Invalid symbol | Symbol not available | Check supported markets |
| Stale data | Orderbook out of sync | Clear and resubscribe |
Use Cases
Market Making
orderbookManager.on('orderbookChange', (event) => {
const { symbol, bestBidAsk } = event;
const spread = parseFloat(bestBidAsk.spread);
if (spread > 1.0) { // Wide spread opportunity
console.log(`Wide spread detected on ${symbol}: ${spread}`);
// Consider placing maker orders
}
});Liquidity Analysis
function analyzeLiquidity(symbol, priceRange = 0.01) {
const orderbook = orderbookManager.getOrderbook(symbol);
const bestBidAsk = orderbookManager.getBestBidAsk(symbol);
const midPrice = (parseFloat(bestBidAsk.bestBid) + parseFloat(bestBidAsk.bestAsk)) / 2;
const rangeMin = midPrice * (1 - priceRange);
const rangeMax = midPrice * (1 + priceRange);
let bidLiquidity = 0;
let askLiquidity = 0;
// Calculate liquidity within price range
orderbook.bids.forEach((quantity, price) => {
const priceNum = parseFloat(price);
if (priceNum >= rangeMin) {
bidLiquidity += parseFloat(quantity) * priceNum;
}
});
orderbook.asks.forEach((quantity, price) => {
const priceNum = parseFloat(price);
if (priceNum <= rangeMax) {
askLiquidity += parseFloat(quantity) * priceNum;
}
});
return {
bidLiquidity,
askLiquidity,
totalLiquidity: bidLiquidity + askLiquidity,
liquidityImbalance: (bidLiquidity - askLiquidity) / (bidLiquidity + askLiquidity)
};
}Implementation Notes
- Initial State: On subscription, you receive a full orderbook snapshot
- Incremental Updates: All subsequent messages contain changes to apply
- Price Level Management: Remove levels when quantity is "0.0"
- Efficient Storage: Use appropriate data structures for your use case
- Rate Limiting: Throttle UI updates for high-frequency markets
- Error Recovery: Implement robust error handling and recovery
Related Endpoints
- Get Orderbook - Get orderbook snapshot via REST
- Market Price Updates - Track price changes
- Get Last Trades - Track trade executions via REST endpoint