For the complete documentation index, see llms.txt. This page is also available as Markdown.

l4BookUpdates

Stream real-time L4 orderbook updates per block.

Every live data message includes both routing identifiers with the same value:

{"type": "l4BookUpdates", "channel": "l4BookUpdates"}

Other payload fields are omitted above. Existing clients may continue routing on either field.

πŸ’§ New endpoint - this endpoint is not a part of original Hyperliquid API and is added by us for builder convenience.

Subscribe

{
    "method": "subscribe",
    "subscription": {
        "type": "l4BookUpdates",
        "coins": ["ETH", "BTC"]
    }
}

Parameters:

Parameter
Type
Required
Description

coins

string[]

No

Coins to subscribe to. Omit for all markets (needs l4BookUpdatesAll add-on).

marketTypes

string[]

No

All-markets only. Filters delivery by market type β€” see All-markets filter. Rejected if combined with coins.

merge

bool

No

Default false. When true, union these coins into the connection's existing coin-list l4BookUpdates slots instead of opening a new stream. Subscribe-only β€” rejected on an unsubscribe, on the all-markets firehose, and on other subscription types.

Adding a coin without a gap is opt-in. Omit merge (or send false) and each subscribe is its own stream: coins:["ETH"] then coins:["BTC"] is two frames per block. Set "merge": true to union the new coins into the stored coin-list set. The next block is one frame covering the merged set β€” no unsubscribe/resubscribe cycle, and no missed or split block. The subscribe ack echoes the merged coin list. Re-sending coins you already hold is a no-op.

The ack is the boundary. Each frame carries the coin set its block was fanned out with, and the ack is delivered after every frame that was already in flight when the subscribe committed. So a frame arriving before the ack may or may not cover a coin that ack names β€” that block was already being built β€” while every frame after it does. Apply a coin-set change when the ack arrives, not when the request is sent.

The server holds the ack for a bounded interval only. If fan-out stalls past it (server-side contention; rare, and alerted on), the ack is released early β€” and every block still being fanned out at that moment can deliver a pre-change frame after it, not just one. Blocks are fanned out concurrently, so treat the boundary as the rule and a run of frames missing a just-added coin as possible-but-exceptional. It resolves once those blocks drain; the coin appears from the first block whose fan-out began after the change.

Two separate limits apply. A single subscribe request may name at most 200 coins, and your tier's coin cap bounds the merged set when merge is true. A subscribe that would push the merged set past your tier cap is rejected and the set is left unchanged.

When a subscribe carries a cursor, replay covers only the coins named in that request. After a merge the ack still echoes the full merged list, so it will name more coins than the replay covers.

If the connection holds one coin-list subscription (a single batched subscribe, or the result of merge: true), unsubscribe with coins subtracts those coins; the rest keep streaming. Coins you do not hold are ignored. If the connection holds several coin-list subscriptions, unsubscribe is exact-match β€” it removes the slot whose coin set matches, and does not collapse the others.

The all-markets firehose (coins omitted) still coexists with a coin-list subscription on the same connection β€” overlapping coins are delivered twice. That is unchanged. A coin-scoped unsubscribe against only the firehose is rejected (all minus ETH is not representable). A bare {type:"l4BookUpdates"} unsubscribe removes the firehose only; it does not clear the coin list.

All-markets filter

When coins is omitted, the optional marketTypes field restricts the firehose to specific market types. Each entry is "perp", "spot", "outcome", or the wildcard "*" (alone) for "every type the server currently tracks":

Omitting marketTypes defaults to ["perp"] β€” outcome and spot markets do not appear unless you opt in. The default never grows; new market types must be added to your marketTypes array explicitly. Pass ["*"] to auto-opt-in to future types.

A second subscribe with a different marketTypes value replaces the previous filter rather than coexisting with it.

Unsubscribe

Name coins to remove. With one coin-list slot those coins are subtracted (remaining coins keep streaming, gap-free). With several coin-list slots the request must match one slot's coin set exactly:

Omit coins to drop the all-markets firehose only. That does not remove a coin-list subscription. To clear the coin-list, unsubscribe the coins you hold (the last coin drops the subscription).

The same boundary rule applies in reverse: a block whose fan-out began before the unsubscribe committed still carries the coin set it started with, and that frame is delivered before the unsubscribe ack. No frame after the ack contains the dropped coins.

Update data format

Updates are streamed per-block with order changes for subscribed markets:

Diff types

Type
Description
Fields

new

New order placed

coin, oid, user, side, px, sz, insertBefore (optional)

update

Order size changed (partial fill)

coin, oid, sz

remove

Order removed (filled/cancelled)

coin, oid

Field definitions

Field
Type
Description

height

int

Block height

timestamp

int

Block timestamp (ms since epoch)

coin

string

Market symbol (e.g., "ETH", "BTC")

oid

int

Unique order ID

user

string

User address (0x...)

side

string

"B" = bid, "A" = ask

px

string

Price as decimal string

sz

string

Size as decimal string

insertBefore

int (oid), optional

On new diffs only: insert this order into its price level's queue immediately before the resting order with this oid (Hyperliquid ALO priority placement). Absent = append to the tail of the queue. If the target oid is no longer at the level, append to the tail.

Examples

Common errors

The subscription feedback and warning shapes are documented in the WebSocket overview. Invalid names are rejected together, for example invalid coin name(s): ["BTC-USD","ETH USD"]. More than 200 names in one request returns too_many_coins with requested and maximum details. A well-formed inactive name succeeds with an inactive_coins warning instead of silently producing no data.

  1. Too many coins - Either one request named more than 200 coins, or a merge: true subscribe would push the stored set past your tier's coin cap. Both leave the existing subscription unchanged.

  2. Subscribing to all markets requires permission - Needs add-on for all books

  3. Rate limit exceeded - Reduce subscription frequency

  4. Authentication failed - Check API key

Last updated