WebSocket API
Stream live markets and replay history on Build, then move to Pro for order-level L4 diffs and order feeds.
Connection Guide
Learn the connection contract, heartbeat rules, and command model before you stream anything.
Open the socket
Connect to wss://api.0xarchive.io/ws with your API key and confirm the socket before you subscribe.
const ws = new WebSocket( "wss://api.0xarchive.io/ws?apiKey=0xa_your_api_key");
ws.onopen = () => { console.log("Connected to 0xArchive WebSocket");};
ws.onmessage = (event) => { const data = JSON.parse(event.data); console.log("Received:", data);};Keep-alive
The server sends ping frames every 30 seconds. Clients that do not answer within 60 seconds are disconnected.
// Send a ping to keep connection alivews.send(JSON.stringify({ op: "ping" }));
// Server responds with:// {"type": "pong"}Command model
The same socket handles subscribe, unsubscribe, replay, and keep-alive commands.
Real-time Subscriptions
subscribeSubscribe to a real-time channel
unsubscribeUnsubscribe from a channel
Historical Replay
replayStart historical replay. Use "channel" for single or "channels" (array) for multi-channel synchronized replay. All channels must be from the same exchange. Use interval for candles (1m-1w). Use granularity for lighter_orderbook.
replay.pausePause current replay
replay.resumeResume paused replay
replay.seekSeek to specific timestamp
replay.stopStop current replay
Utility
pingSend a ping to keep connection alive (server responds with pong)
Channels
Pick the channels, mode limits, and order book depth before you design the consumer.
Hyperliquid
Historical data from April 2023. Symbols: BTC, ETH, SOL, etc.
| Channel | Description | Mode | Tier |
|---|---|---|---|
orderbook | L2 order book snapshots (~1.2s resolution) | Real-time & Historical | Build+ |
trades | Trade/fill updates (pre-March 2025 fills are taker-only) | Real-time & Historical | Build+ |
candles | OHLCV candles. Interval param: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w | Historical only (May 2025+) | Build+ |
open_interest | Open interest snapshots | Historical only (May 2023+) | Build+ |
funding | Funding rate snapshots | Historical only (May 2023+) | Build+ |
liquidations | Liquidation events | Historical only (December 2025+) | Build+ |
ticker | Price and 24h volume | Real-time only | Build+ |
all_tickers | All market tickers at once (no symbol param needed) | Real-time only | Build+ |
l4_diffs | L4 orderbook diffs with user wallet attribution. Batched by block (~100ms). | Real-time only | Pro+ |
l4_orders | Order lifecycle events (new, partial fill, filled, cancelled) with wallet attribution. Batched by block. | Real-time only | Pro+ |
HIP-3
Builder-deployed perpetuals on Hyperliquid. Data from December 2025. Symbols: km:US500, hyna:BTC, etc.
| Channel | Description | Mode | Tier |
|---|---|---|---|
hip3_orderbook | L2 order book snapshots | Historical only | Pro+ |
hip3_trades | Trade/fill updates | Historical only | Build+ |
hip3_candles | OHLCV candles. Interval param: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w | Historical only | Build+ |
hip3_open_interest | Open interest snapshots | Historical only | Build+ |
hip3_funding | Funding rate snapshots | Historical only | Build+ |
hip3_liquidations | Liquidation events with long/short direction | Historical only | Build+ |
hip3_l4_diffs | L4 orderbook diffs with user wallet attribution. Batched by block (~100ms). | Real-time only | Pro+ |
hip3_l4_orders | Order lifecycle events with wallet attribution. Batched by block. | Real-time only | Pro+ |
Lighter.xyz
Data from August 2025 for fills and January 2026 for orderbook / OI / funding. Symbols: BTC, ETH, SOL, etc.
| Channel | Description | Mode | Tier |
|---|---|---|---|
lighter_orderbook | Full-depth order book. Granularity param: checkpoint, 30s, 10s, 1s, tick | Historical only | Build+ |
lighter_trades | Trade/fill updates | Historical only | Build+ |
lighter_candles | OHLCV candles. Interval param: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w | Historical only | Build+ |
lighter_open_interest | Open interest snapshots | Historical only | Build+ |
lighter_funding | Funding rate snapshots | Historical only | Build+ |
lighter_l3_orderbook | L3 order-level orderbook snapshots with individual order IDs and sizes | Historical only | Pro+ |
Real-time subscriptions
Subscribe to live market data feeds when you need proxied venue updates with minimal latency.
// Subscribe to real-time order book updatesws.send(JSON.stringify({ op: "subscribe", channel: "orderbook", symbol: "BTC"}));
// Subscribe to real-time tradesws.send(JSON.stringify({ op: "subscribe", channel: "trades", symbol: "ETH"}));
// Subscribe to L4 orderbook diffs (Pro+, real-time only)// Messages are batched by block and include user wallet addressesws.send(JSON.stringify({ op: "subscribe", channel: "l4_diffs", symbol: "BTC"}));
// Subscribe to L4 order lifecycle events (Pro+, real-time only)// Streams order state changes (new, partial fill, filled, cancelled)ws.send(JSON.stringify({ op: "subscribe", channel: "l4_orders", symbol: "ETH"}));
// Subscribe to HIP-3 L4 channelsws.send(JSON.stringify({ op: "subscribe", channel: "hip3_l4_diffs", symbol: "km:US500"}));
ws.send(JSON.stringify({ op: "subscribe", channel: "hip3_l4_orders", symbol: "km:US500"}));
// Unsubscribews.send(JSON.stringify({ op: "unsubscribe", channel: "orderbook", symbol: "BTC"}));L4 orderbook streaming
L4 channels deliver a full snapshot first, then continuous diff batches so you can reconstruct the live order-level book.
import websockets, json, asyncio
async def stream_l4(): uri = "wss://api.0xarchive.io/ws?apiKey=YOUR_KEY" async with websockets.connect(uri, max_size=20_000_000) as ws: # Subscribe to L4 diffs (works for l4_diffs or hip3_l4_diffs) await ws.send(json.dumps({ "op": "subscribe", "channel": "l4_diffs", "symbol": "BTC" }))
book = {"bids": {}, "asks": {}} snapshot_ts = None
async for msg in ws: data = json.loads(msg)
if data["type"] == "l4_snapshot": # Full book delivered first (every order including triggers) snapshot_ts = data["timestamp"] for order in data["data"]["bids"]: book["bids"][order["oid"]] = order for order in data["data"]["asks"]: book["asks"][order["oid"]] = order print(f"Snapshot: {len(book['bids'])} bids, {len(book['asks'])} asks")
elif data["type"] == "l4_batch" and snapshot_ts is not None: # Apply diffs to maintain the book for diff in data["data"]: if diff["ts"] <= snapshot_ts: continue side = "bids" if diff["side"] == "B" else "asks" oid = diff["oid"] if diff["dt"] == "new": book[side][oid] = { "oid": oid, "side": diff["side"], "price": diff["px"], "size": diff["sz"], "user_address": diff["user"] } elif diff["dt"] == "update": if oid in book[side]: book[side][oid]["size"] = diff["sz"] elif diff["dt"] == "remove": book[side].pop(oid, None)
asyncio.run(stream_l4())Replay
The same socket handles historical replay, gap handling, and backtesting.
Historical replay
Replay historical data with original timing preserved and adjustable playback speed.
// Start historical replay at 10x speed (Hyperliquid)ws.send(JSON.stringify({ op: "replay", channel: "orderbook", symbol: "BTC", start: 1681516800000, // Unix timestamp in ms end: 1681603200000, speed: 10 // 10x playback speed}));
// Start Lighter.xyz orderbook replay with granularityws.send(JSON.stringify({ op: "replay", channel: "lighter_orderbook", symbol: "BTC", start: 1706140800000, end: 1706227200000, speed: 10, granularity: "10s" // Options: checkpoint, 30s, 10s, 1s, tick}));
// Replay candles with specific intervalws.send(JSON.stringify({ op: "replay", channel: "candles", symbol: "ETH", start: 1681516800000, end: 1681603200000, speed: 10, interval: "1h" // Options: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w}));
// Replay OI/funding dataws.send(JSON.stringify({ op: "replay", channel: "open_interest", symbol: "BTC", start: 1684540800000, end: 1684627200000, speed: 10}));
// Multi-channel synchronized replay (all channels must be same exchange)// Data is interleaved in timestamp order across channelsws.send(JSON.stringify({ op: "replay", channels: ["orderbook", "trades", "funding"], // Use "channels" (plural) for multi-channel symbol: "BTC", start: 1681516800000, end: 1681603200000, speed: 10}));
// Multi-channel Lighter replayws.send(JSON.stringify({ op: "replay", channels: ["lighter_orderbook", "lighter_trades", "lighter_funding"], symbol: "ETH", start: 1706140800000, end: 1706227200000, speed: 10, granularity: "10s" // Applies to lighter_orderbook channel}));
// Multi-channel replay sends "replay_snapshot" messages before the timeline// starts, providing initial state for each channel:// {"type": "replay_snapshot", "channel": "orderbook", "coin": "BTC", "symbol": "BTC", "timestamp": ..., "data": {...}}// {"type": "replay_snapshot", "channel": "funding", "coin": "BTC", "symbol": "BTC", "timestamp": ..., "data": {...}}// Then interleaved historical_data messages follow in timestamp order
// Pause replayws.send(JSON.stringify({ op: "replay.pause" }));
// Resume replayws.send(JSON.stringify({ op: "replay.resume" }));
// Seek to specific timestampws.send(JSON.stringify({ op: "replay.seek", timestamp: 1681550000000}));
// Stop replayws.send(JSON.stringify({ op: "replay.stop" }));Gap detection
Replay emits gap_detected whenever continuity falls below the threshold.
// Gap detection during replayws.onmessage = (event) => { const msg = JSON.parse(event.data);
if (msg.type === 'gap_detected') { console.log(`Gap in ${msg.channel}/${msg.symbol}:`); console.log(` From: ${new Date(msg.gap_start).toISOString()}`); console.log(` To: ${new Date(msg.gap_end).toISOString()}`); console.log(` Duration: ${msg.duration_minutes} minutes`); }
// Handle other message types... if (msg.type === 'historical_data') { // Process data }};Gap thresholds
- - orderbook, candles, liquidations: 2 minutes
- - trades: 60 minutes
Backtesting guide
Feed synchronized historical market data through the same event loop you use for live handling.
import asyncio, json, websockets
async def backtest_strategy(): uri = "wss://api.0xarchive.io/ws?apiKey=0xa_your_api_key" async with websockets.connect(uri) as ws: # Replay orderbook + trades together at 50x speed await ws.send(json.dumps({ "op": "replay", "channels": ["orderbook", "trades"], "symbol": "BTC", "start": 1704067200000, # Jan 1 2024 "end": 1704153600000, # Jan 2 2024 "speed": 50 }))
orderbook = None trades = []
async for message in ws: msg = json.loads(message)
if msg["type"] == "replay_snapshot": # Initial state before timeline starts if msg["channel"] == "orderbook": orderbook = msg["data"]
elif msg["type"] == "historical_data": if msg["channel"] == "orderbook": orderbook = msg["data"] # Run strategy on each orderbook update signal = my_strategy(orderbook, trades) if signal: execute_paper_trade(signal, msg["timestamp"]) elif msg["channel"] == "trades": trades.append(msg["data"])
elif msg["type"] == "gap_detected": print(f"Data gap: {msg['duration_minutes']}min at {msg['gap_start']}")
elif msg["type"] == "replay_completed": print(f"Backtest done. {msg['snapshots_sent']} data points processed.") break
asyncio.run(backtest_strategy())Tips
- Use multi-channel replay (orderbook + trades + funding) to get a complete market picture with synchronized timestamps.
- Begin at low speed (1-10x) to verify your strategy logic, then increase speed for full backtests.
- Handle replay_snapshot messages first to build initial state before the timeline begins.
- Monitor gap_detected messages: gaps in orderbook data (>2 min) or trades (>60 min) indicate periods where your backtest may be unreliable.
- Replay is limited to 1 task per connection. Open multiple connections to backtest different symbols in parallel.
- For bulk analysis over long periods, consider the REST API with cursor pagination instead of replay (no speed limit, lower credit cost).
Limits
Check subscription, replay, and connection constraints before you commit client architecture or concurrency assumptions.
WebSocket limits by tier
| Tier | Symbols | Historical Depth | Max Range/Request | Max Subscriptions | Max Replay Speed |
|---|---|---|---|---|---|
| Free | No WebSocket access | - | - | - | - |
| Build | All | 365 days | Unlimited | 25 | 50x |
| Pro | All | Unlimited | Unlimited | 100 | 100x |
| Enterprise | All | Unlimited | Unlimited | 200 | 1000x |
WebSocket access is available on Build tier and above. See pricing for the current tier posture.
Connection constraints
| Constraint | Limit | Description |
|---|---|---|
| Message metering | 1 credit/msg | Each WebSocket message counts as 1 API credit toward monthly quota |
| Idle timeout (with subscriptions) | 60 seconds | Send ping every 30s to keep alive |
| Idle timeout (no subscriptions) | 5 minutes | Connections without subscriptions timeout after 5 minutes |
| Subscription rate limit | 10/second | Max subscribe/unsubscribe operations per second |
| Concurrent replay tasks | 1 | One replay per connection at a time (multi-channel counts as one replay) |
| Multi-channel constraint | Same exchange | All channels in a multi-channel replay must be from the same exchange (e.g., all Hyperliquid or all Lighter) |
| Per-request range limit | Tier-based | Free: 30 days max, Build/Pro/Enterprise: Unlimited |
| Data availability | Varies by channel | Hyperliquid: April 2023+ (OI/funding: May 2023+, candles: May 2025+, liquidations: December 2025+, L4: March 2026+). Lighter: August 2025+ (fills), January 2026+ (orderbook/OI/funding). HIP-3: December 2025+. |