WebSocket API

Stream live markets and replay history on Build, then move to Pro for order-level L4 diffs and order feeds.

Endpoint
wss://api.0xarchive.io/ws
Modes
Live and replay
Replay
50x on Build, 100x on Pro
Status
Live health on /status

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.

JavaScript
// Send a ping to keep connection alive
ws.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

subscribe

Subscribe to a real-time channel

Params: channel, symbol

unsubscribe

Unsubscribe from a channel

Params: channel, symbol

Historical Replay

replay

Start 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.

Params: channel|channels, symbol, start, end, speed, [interval], [granularity]

replay.pause

Pause current replay

Params: -

replay.resume

Resume paused replay

Params: -

replay.seek

Seek to specific timestamp

Params: timestamp

replay.stop

Stop current replay

Params: -

Utility

ping

Send a ping to keep connection alive (server responds with pong)

Params: -

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.

ChannelDescriptionModeTier
orderbookL2 order book snapshots (~1.2s resolution)Real-time & HistoricalBuild+
tradesTrade/fill updates (pre-March 2025 fills are taker-only)Real-time & HistoricalBuild+
candlesOHLCV candles. Interval param: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1wHistorical only (May 2025+)Build+
open_interestOpen interest snapshotsHistorical only (May 2023+)Build+
fundingFunding rate snapshotsHistorical only (May 2023+)Build+
liquidationsLiquidation eventsHistorical only (December 2025+)Build+
tickerPrice and 24h volumeReal-time onlyBuild+
all_tickersAll market tickers at once (no symbol param needed)Real-time onlyBuild+
l4_diffsL4 orderbook diffs with user wallet attribution. Batched by block (~100ms).Real-time onlyPro+
l4_ordersOrder lifecycle events (new, partial fill, filled, cancelled) with wallet attribution. Batched by block.Real-time onlyPro+

HIP-3

Builder-deployed perpetuals on Hyperliquid. Data from December 2025. Symbols: km:US500, hyna:BTC, etc.

ChannelDescriptionModeTier
hip3_orderbookL2 order book snapshotsHistorical onlyPro+
hip3_tradesTrade/fill updatesHistorical onlyBuild+
hip3_candlesOHLCV candles. Interval param: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1wHistorical onlyBuild+
hip3_open_interestOpen interest snapshotsHistorical onlyBuild+
hip3_fundingFunding rate snapshotsHistorical onlyBuild+
hip3_liquidationsLiquidation events with long/short directionHistorical onlyBuild+
hip3_l4_diffsL4 orderbook diffs with user wallet attribution. Batched by block (~100ms).Real-time onlyPro+
hip3_l4_ordersOrder lifecycle events with wallet attribution. Batched by block.Real-time onlyPro+

Lighter.xyz

Data from August 2025 for fills and January 2026 for orderbook / OI / funding. Symbols: BTC, ETH, SOL, etc.

ChannelDescriptionModeTier
lighter_orderbookFull-depth order book. Granularity param: checkpoint, 30s, 10s, 1s, tickHistorical onlyBuild+
lighter_tradesTrade/fill updatesHistorical onlyBuild+
lighter_candlesOHLCV candles. Interval param: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1wHistorical onlyBuild+
lighter_open_interestOpen interest snapshotsHistorical onlyBuild+
lighter_fundingFunding rate snapshotsHistorical onlyBuild+
lighter_l3_orderbookL3 order-level orderbook snapshots with individual order IDs and sizesHistorical onlyPro+

Real-time subscriptions

Subscribe to live market data feeds when you need proxied venue updates with minimal latency.

JavaScript
// Subscribe to real-time order book updates
ws.send(JSON.stringify({
op: "subscribe",
channel: "orderbook",
symbol: "BTC"
}));
// Subscribe to real-time trades
ws.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 addresses
ws.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 channels
ws.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"
}));
// Unsubscribe
ws.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.

Python
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.

JavaScript
// 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 granularity
ws.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 interval
ws.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 data
ws.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 channels
ws.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 replay
ws.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 replay
ws.send(JSON.stringify({ op: "replay.pause" }));
// Resume replay
ws.send(JSON.stringify({ op: "replay.resume" }));
// Seek to specific timestamp
ws.send(JSON.stringify({
op: "replay.seek",
timestamp: 1681550000000
}));
// Stop replay
ws.send(JSON.stringify({ op: "replay.stop" }));

Gap detection

Replay emits gap_detected whenever continuity falls below the threshold.

JavaScript
// Gap detection during replay
ws.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.

Python
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

TierSymbolsHistorical DepthMax Range/RequestMax SubscriptionsMax Replay Speed
FreeNo WebSocket access----
BuildAll365 daysUnlimited2550x
ProAllUnlimitedUnlimited100100x
EnterpriseAllUnlimitedUnlimited2001000x

WebSocket access is available on Build tier and above. See pricing for the current tier posture.

Connection constraints

ConstraintLimitDescription
Message metering1 credit/msgEach WebSocket message counts as 1 API credit toward monthly quota
Idle timeout (with subscriptions)60 secondsSend ping every 30s to keep alive
Idle timeout (no subscriptions)5 minutesConnections without subscriptions timeout after 5 minutes
Subscription rate limit10/secondMax subscribe/unsubscribe operations per second
Concurrent replay tasks1One replay per connection at a time (multi-channel counts as one replay)
Multi-channel constraintSame exchangeAll channels in a multi-channel replay must be from the same exchange (e.g., all Hyperliquid or all Lighter)
Per-request range limitTier-basedFree: 30 days max, Build/Pro/Enterprise: Unlimited
Data availabilityVaries by channelHyperliquid: 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+.

Keep exploring WebSocket