Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Reliability & Consistency14 min read

Hyperliquid l2Book WebSocket: Maintain a Consistent Local Order Book

A practical guide to subscribing to Hyperliquid's l2Book channel, parsing snapshot levels, detecting gaps, and re-seeding a reliable local order book.

TL;DR

Hyperliquid's l2Book WebSocket channel delivers order book updates as a levels array containing two sub-arrays: bids and asks, each entry formatted as [price, size, order count]. The channel is documented as a full snapshot per message, so the correct local book algorithm is to replace the entire book on each message rather than apply incremental diffs. To avoid a transient wrong book, seed from the REST info endpoint's l2Book POST, then switch to the WebSocket stream and reconcile the first snapshot against the seed. Detect gaps using the message time field and a monotonic sequence, and on any heartbeat timeout or reconnect, discard the local book and re-seed from REST plus the first fresh WebSocket snapshot. Production consumers should periodically reconcile against a fresh REST snapshot and never trust a long-lived local book indefinitely.

Hyperliquid l2Book Channel Subscription Mechanics

The l2Book channel is a public WebSocket subscription that streams aggregated order book levels for a single coin. To subscribe, send a JSON message with method 'subscribe' and a subscription object containing type 'l2Book' and the coin symbol, for example { method: 'subscribe', subscription: { type: 'l2Book', coin: 'BTC' } }. The coin naming convention follows Hyperliquid's market symbols, typically uppercase tickers such as 'BTC', 'ETH', or 'SOL'. The subscription model and full channel list are introduced in the Hyperliquid WebSocket subscriptions guide.

Each l2Book message contains a levels array with exactly two sub-arrays: the first is bids, the second is asks. Every entry is a three-element array [px, sz, n] where px is the price string, sz is the size string, and n is the number of orders at that level. The payload shape is documented in the Hyperliquid WebSocket subscriptions documentation. Because the channel is documented as sending full snapshots, the update algorithm is a wholesale replacement of the local book, not a diff application.

The message also includes a time field. Treat this as the authoritative timestamp for ordering and freshness checks. A monotonically increasing sequence, whether derived from time or a separate counter, is essential for detecting out-of-order or dropped updates. If you are new to WebSocket reliability patterns, the RPC WebSocket reconnect and gap recovery article covers the general mechanics that apply here.

  • Subscribe with { method: 'subscribe', subscription: { type: 'l2Book', coin: 'BTC' } }.
  • Coin symbols are uppercase market tickers; verify against the exchange's market list.
  • The levels array is always [bids, asks] with entries [px, sz, n].
  • Treat each message as a full snapshot unless provider documentation states otherwise.

Parsing and Sorting l2Book Levels into a Canonical Book

After receiving a message, parse the levels array into two separate collections: bids and asks. For bids, sort by price descending so the best bid is first. For asks, sort by price ascending so the best ask is first. Store prices and sizes as strings or high-precision decimals to avoid floating-point rounding errors. The order count n is useful for liquidity analysis but does not affect price-time priority in the aggregated book.

A canonical book representation should expose best bid, best ask, and spread. The spread is best ask minus best bid. If either side is empty, the book is one-sided and the spread is undefined. Always validate that prices are positive and sizes are non-negative; malformed entries should be logged and skipped rather than corrupting the local state.

Because l2Book messages are full snapshots, you do not need to merge levels. Replace the entire bid and ask collections on each message. This simplifies consistency: the local book is exactly the last message's levels, sorted canonically. The trade-off is that you cannot detect a dropped message by missing a diff; you must rely on time and sequence checks.

  • Bids: sort descending by price; asks: sort ascending by price.
  • Use string or decimal arithmetic for px and sz.
  • Replace the whole book on each snapshot; do not merge.
  • Validate entries and log anomalies instead of applying them.

Snapshot vs Incremental Semantics and Sequence Tracking

Hyperliquid's l2Book channel is documented as sending full snapshots, but the exact diff/sequence semantics and any checksum field are documented per channel and can change. Always confirm the current behavior against the Hyperliquid WebSocket subscriptions documentation before assuming snapshot-only. If a channel ever switches to incremental diffs, the update algorithm changes to applying price-level updates and removals, and you must track a sequence number to detect gaps.

The message time field provides a coarse ordering signal. If you receive a message with a time earlier than the last applied message, treat it as out-of-order and discard it. For stronger guarantees, maintain a monotonic sequence counter if the provider exposes one. Without a sequence, you can only detect gaps by comparing against a fresh REST snapshot periodically.

Message ordering across multiple coins is not guaranteed globally. If you subscribe to l2Book for BTC and ETH, the relative order of their messages is not a reliable indicator of market-wide ordering. Maintain separate sequence tracking per coin and never assume cross-coin causality.

  • Documented behavior: l2Book sends full snapshots; verify per channel.
  • Use time and any available sequence to detect out-of-order or dropped updates.
  • Cross-coin message ordering is not globally guaranteed.
  • If diffs are introduced, switch to apply-and-remove logic with sequence checks.

Seeding the Local Book from the REST Info Endpoint

Before opening the WebSocket, seed the local book from the REST info endpoint. Send a POST request to /info with body { type: 'l2Book', coin: 'BTC' }. The response contains the same levels structure as the WebSocket channel. This seed gives you an immediate, consistent starting point. The endpoint is documented in the Hyperliquid info endpoint documentation.

After seeding, open the WebSocket and subscribe to l2Book. The first WebSocket snapshot may differ from the REST seed due to the time elapsed between the two calls. Reconcile by replacing the local book with the first WebSocket snapshot, but log the divergence between the seed and the first snapshot. If the divergence exceeds a threshold you define, alert and consider re-seeding. This avoids a transient wrong book where the local state is a mix of stale REST data and fresh WebSocket data.

A common mistake is to apply the first WebSocket snapshot as a diff against the REST seed. Because l2Book is a full snapshot, you must replace, not merge. The reconciliation step is purely observational: compare, log, then replace.

  • Seed with POST /info { type: 'l2Book', coin: 'BTC' }.
  • Open WebSocket and subscribe to l2Book for the same coin.
  • Replace the local book with the first WebSocket snapshot.
  • Log seed-vs-snapshot divergence for observability.
const seedBook = async (coin) => {
  const res = await fetch('https://api.hyperliquid.xyz/info', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ type: 'l2Book', coin })
  });
  const data = await res.json();
  return data.levels; // [bids, asks]
};

// Usage
seedBook('BTC').then(levels => {
  console.log('Seed bids:', levels[0].length, 'asks:', levels[1].length);
});

Detecting and Recovering from Gaps and Reconnects

A gap occurs when the local book misses one or more updates. With full snapshots, a missed message means the local book is stale until the next snapshot arrives. If the next snapshot arrives quickly, the gap self-heals. If the socket is silent, the local book can remain stale but plausible, which is dangerous for trading logic. Use a heartbeat timeout to detect silence. The Hyperliquid WebSocket heartbeat and keepalive detection article covers keepalive mechanics in detail.

On any heartbeat timeout or reconnect, discard the local book entirely. Do not attempt to apply a partial diff or resume from the last known state. Re-seed from REST plus the first fresh WebSocket snapshot. This guarantees a consistent starting point. The general pattern is described in RPC WebSocket reconnect and gap recovery.

If you detect a time regression or a sequence jump, treat it as a gap. Discard and re-seed. The cost of re-seeding is a brief period without a book, which is safer than trading on a stale book. For production systems, consider a circuit breaker that pauses trading logic until the book is re-seeded and validated.

  • Heartbeat timeout or reconnect: discard local book.
  • Re-seed from REST plus first fresh WebSocket snapshot.
  • Time regression or sequence jump: treat as gap, re-seed.
  • Pause dependent logic until the book is validated.

Runnable Node.js Example: l2Book Subscription and Book Maintenance

The following Node.js example subscribes to l2Book for BTC, maintains sorted bid and ask maps, computes best bid/ask and spread, and re-seeds on reconnect. It uses the ws package for WebSocket and fetch for the REST seed. Replace the WebSocket URL with your provider's endpoint. For endpoint options, see Hyperliquid RPC endpoints (RPC Assistant).

The example treats each message as a full snapshot and replaces the local book. It tracks the last message time and discards out-of-order messages. On reconnect, it re-seeds from REST before resubscribing. This is a minimal but correct starting point; add logging, metrics, and alerting for production.

const WebSocket = require('ws');

const COIN = 'BTC';
const WS_URL = 'wss://api.hyperliquid.xyz/ws';
const INFO_URL = 'https://api.hyperliquid.xyz/info';

let bids = new Map(); // price -> size
let asks = new Map();
let lastTime = 0;

const seed = async () => {
  const res = await fetch(INFO_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ type: 'l2Book', coin: COIN })
  });
  const data = await res.json();
  applySnapshot(data.levels);
};

const applySnapshot = (levels) => {
  bids = new Map(levels[0].map(([px, sz]) => [px, sz]));
  asks = new Map(levels[1].map(([px, sz]) => [px, sz]));
};

const bestBid = () => {
  let best = null;
  for (const [px] of bids) {
    if (best === null || parseFloat(px) > parseFloat(best)) best = px;
  }
  return best;
};

const bestAsk = () => {
  let best = null;
  for (const [px] of asks) {
    if (best === null || parseFloat(px) < parseFloat(best)) best = px;
  }
  return best;
};

const connect = async () => {
  await seed();
  const ws = new WebSocket(WS_URL);

  ws.on('open', () => {
    ws.send(JSON.stringify({
      method: 'subscribe',
      subscription: { type: 'l2Book', coin: COIN }
    }));
  });

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    if (msg.channel !== 'l2Book') return;
    if (msg.data.time < lastTime) return; // out-of-order
    lastTime = msg.data.time;
    applySnapshot(msg.data.levels);
    const bb = bestBid();
    const ba = bestAsk();
    if (bb && ba) {
      console.log('Best bid:', bb, 'Best ask:', ba, 'Spread:', parseFloat(ba) - parseFloat(bb));
    }
  });

  ws.on('close', () => {
    console.log('Reconnecting...');
    setTimeout(connect, 1000);
  });
};

connect();

Reproducible Consistency Check Against REST Snapshots

To verify your local book, periodically fetch a fresh REST snapshot and compare the best bid, best ask, and top-of-book sizes against your local state. Run this check on a fixed interval, for example every 30 seconds, and record the divergence. If the divergence exceeds a threshold you define, alert and re-seed. This method is reproducible and does not rely on provider-specific guarantees.

Use the following table to record your measurements. Fill it with values from your own endpoint and environment. Do not rely on benchmark numbers from other sources; measure your own.

  • Fetch REST snapshot via POST /info { type: 'l2Book', coin: 'BTC' }.
  • Compare local best bid/ask and top sizes against the snapshot.
  • Record divergence and alert if above threshold.
  • Re-seed on persistent divergence.
const checkConsistency = async (localBids, localAsks) => {
  const res = await fetch('https://api.hyperliquid.xyz/info', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ type: 'l2Book', coin: 'BTC' })
  });
  const data = await res.json();
  const restBids = data.levels[0];
  const restAsks = data.levels[1];
  const restBestBid = restBids[0]?.[0];
  const restBestAsk = restAsks[0]?.[0];
  const localBestBid = [...localBids.keys()].sort((a,b) => parseFloat(b) - parseFloat(a))[0];
  const localBestAsk = [...localAsks.keys()].sort((a,b) => parseFloat(a) - parseFloat(b))[0];
  console.log('REST best bid:', restBestBid, 'Local best bid:', localBestBid);
  console.log('REST best ask:', restBestAsk, 'Local best ask:', localBestAsk);
  if (restBestBid !== localBestBid || restBestAsk !== localBestAsk) {
    console.warn('Divergence detected');
  }
};

Results Table for Endpoint and Environment Measurements

Use this table to record your own measurements. The columns are designed to capture the consistency and freshness of your local book against REST snapshots. Run the consistency check at a fixed interval and log the results. This is a verified-by-the-reader method; no benchmark numbers are provided because they depend on your network, provider, and load.

Populate the table with your own data. If you see frequent divergences, investigate your WebSocket connection, heartbeat settings, and re-seed logic. The Hyperliquid WebSocket trades and fills channel mechanics article may help you correlate book updates with trade activity.

  • Timestamp: when the check ran.
  • Local best bid/ask: from your local book.
  • REST best bid/ask: from the fresh snapshot.
  • Divergence: absolute difference in price or size.
  • Action: none, alert, or re-seed.

Troubleshooting Common l2Book Consistency Failures

If your local book diverges frequently, check whether you are treating l2Book messages as diffs instead of snapshots. Applying a snapshot as a diff will corrupt the book. Verify that you replace the entire bid and ask collections on each message. If you are merging, switch to replacement.

If you see out-of-order messages, ensure you are comparing the time field and discarding older messages. If the provider does not guarantee ordering, you may need to buffer and sort by time. If you see gaps, verify your heartbeat timeout is not too long; a silent socket can leave a stale book. The Hyperliquid WebSocket heartbeat and keepalive detection article covers detection.

If re-seeding fails, check your REST endpoint and rate limits. The RPC pricing page describes plan limits. For endpoint options, see Hyperliquid RPC endpoints (RPC Assistant). If you need a managed API service, see API service.

  • Symptom: book diverges after first message. Cause: applying snapshot as diff. Fix: replace entire book.
  • Symptom: stale book with no updates. Cause: silent socket. Fix: heartbeat timeout and re-seed.
  • Symptom: out-of-order updates. Cause: no time check. Fix: discard older messages.
  • Symptom: re-seed fails. Cause: REST errors or rate limits. Fix: check endpoint and plan.

Limitations and Tradeoffs of Local Order Book Maintenance

The exact diff/sequence semantics and any checksum field are documented per channel and can change. Always verify against the Hyperliquid WebSocket subscriptions documentation. Message ordering across multiple coins is not guaranteed globally, so do not assume cross-coin causality. A local book is only as fresh as the last applied message; a silent socket can leave a stale-but-plausible book that looks correct but is not.

Production consumers should reconcile periodically rather than trust a long-lived local book indefinitely. The cost of reconciliation is additional REST calls, which may be subject to rate limits. Balance freshness against rate limits based on your trading strategy. For low-latency trading, consider a dedicated provider; see Hyperliquid RPC endpoints (RPC Assistant).

No local book can guarantee perfect consistency without a sequence number and checksum from the provider. If the provider does not expose these, your best defense is frequent reconciliation and conservative re-seeding. Document your assumptions and monitor divergence.

  • Diff/sequence semantics are documented per channel and can change.
  • Cross-coin ordering is not globally guaranteed.
  • Silent sockets can leave stale-but-plausible books.
  • Reconcile periodically; do not trust indefinitely.

Next Steps for Production-Grade Order Book Consumers

Start by implementing the seed-and-replace pattern with a heartbeat timeout and re-seed on reconnect. Add the consistency check against REST snapshots and log divergence. Once stable, add metrics and alerting. For a broader overview of Hyperliquid on OnFinality, see Hyperliquid and the OnFinality Learn hub.

If you need managed WebSocket endpoints with reliability features, explore API service and RPC pricing. For endpoint selection, see Hyperliquid RPC endpoints (RPC Assistant). Continue with the Hyperliquid WebSocket subscriptions guide and Hyperliquid WebSocket trades and fills channel mechanics to deepen your understanding.

Finally, review the RPC WebSocket reconnect and gap recovery article for general patterns that apply beyond Hyperliquid. Test your implementation under adverse conditions: network drops, provider restarts, and high volatility. Only then trust your local book for automated decisions.

  • Implement seed-and-replace with heartbeat timeout.
  • Add consistency checks and divergence logging.
  • Use managed endpoints for reliability.
  • Test under adverse conditions before trusting the book.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started