> ## Documentation Index
> Fetch the complete documentation index at: https://goldrush.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# l4BookUpdates | Hyperliquid WebSocket API

> Hyperliquid l4BookUpdates: subscribe to a strictly-ordered, diff-only order-level (L4) book stream with per-coin sequence numbers and a server epoch for gap detection.

<CardGroup cols={2}>
  <Card title="Credit Cost"> 1 per coin per minute</Card>
  <Card title="Processing"> Realtime</Card>
</CardGroup>

<Info>
  * GoldRush-native. `l4BookUpdates` is not exposed on `wss://api.hyperliquid.xyz/ws`. Pointing a client at the public endpoint with this subscription type will fail.
  * **Order-level (L4) diffs.** Like [`l4Book`](/docs/api-reference/hyperliquid-websocket/l4-book), every change is per **individual order** (`user`, `oid`, `side`, `raw_book_diff`) plus the order-lifecycle `order_statuses` for the block — not aggregated price levels.
  * **Strictly block-ordered.** Diffs are emitted synchronously in monotonic `block_height` order. This is the difference from [`l4Book`](/docs/api-reference/hyperliquid-websocket/l4-book), whose per-block broadcast can deliver adjacent blocks out of order. The `l4Book` channel is unchanged; `l4BookUpdates` is the strictly-ordered, sequenced sibling.
  * **Diff-only — no inline snapshot.** This stream never sends a `Snapshot` frame; every message is an `Updates` batch. Seed your local book from the REST [`l4BookSnapshot`](/docs/api-reference/hyperliquid-info/l4-book-snapshot) info method, then apply diffs on top — see [Bootstrapping & gap detection](#bootstrapping--gap-detection).
  * **Sequenced for gap detection.** Each per-coin diff carries a monotonic `seq` and the `prev_seq` it follows; each batch carries a server `epoch` and a `cursor`. This lets a client detect a dropped message or a server reset and re-bootstrap deterministically.
  * **Perps only.** No spot or prediction-market assets. `coin` is required — subscribe with a single symbol or an array of symbols; one asset per book.
</Info>

**Note:** `l4BookUpdates` bills a **flat** rate per subscribed coin per minute, with **no BTC premium** (unlike [`l4Book`](/docs/api-reference/hyperliquid-websocket/l4-book)). A multi-coin array bills the sum across its coins.

## Endpoint

```
wss://hypercore.goldrushdata.com/ws?key=<GOLDRUSH_API_KEY>
```

<ParamField query="key" type="string" required>
  Your GoldRush API key. Passed as a query parameter at connection time - no `Authorization` header is used.
</ParamField>

## Subscribe

Send this JSON message after the connection is established:

<ParamField body="method" type="string" required>
  Always `"subscribe"`.
</ParamField>

<ParamField body="subscription" type="object" required>
  <Expandable title="properties">
    <ResponseField name="type" type="string" required>Always `"l4BookUpdates"`.</ResponseField>

    <ResponseField name="coin" type="string | string[]" required>
      Asset filter. Perps only. Accepts two shapes:

      * **String** - a single perp symbol (e.g. `"BTC"`, `"HYPE"`). For HIP-3 deployer-perps, include the deployer prefix.
      * **Array of strings** - a fixed list of perp symbols (e.g. `["BTC", "ETH", "HYPE"]`, up to 64). Each coin is sequenced independently and billed independently.

      Unlike [`l2BookDiff2`](/docs/api-reference/hyperliquid-websocket/l2-book-diff2), there is no spot/outcome market filter — `l4BookUpdates` is perp-only.
    </ResponseField>
  </Expandable>
</ParamField>

### Example

| Subscribe with | What you receive |
| - | - |
| `{"type":"l4BookUpdates","coin":"BTC"}` | Order-level diffs for **BTC only** |
| `{"type":"l4BookUpdates","coin":["BTC","ETH","HYPE"]}` | Diffs for **a fixed list of perps** |

<CodeGroup>
  ```bash wscat theme={null}
  wscat -c "wss://hypercore.goldrushdata.com/ws?key=$GOLDRUSH_API_KEY"

  > {"method":"subscribe","subscription":{"type":"l4BookUpdates","coin":"BTC"}}
  ```

  ```typescript TypeScript theme={null}
  import WebSocket from "ws";

  const ws = new WebSocket(
    `wss://hypercore.goldrushdata.com/ws?key=${process.env.GOLDRUSH_API_KEY}`,
  );

  ws.on("open", () => {
    ws.send(JSON.stringify({
      method: "subscribe",
      subscription: { type: "l4BookUpdates", coin: "BTC" },
    }));
  });

  ws.on("message", (raw) => {
    const msg = JSON.parse(raw.toString());
    if (msg.channel !== "l4BookUpdates") return;

    const { block_height, epoch, book_diffs } = msg.data.Updates;
    for (const c of book_diffs) {
      console.log(block_height, epoch, c.coin, "seq", c.prev_seq, "->", c.seq);
      for (const d of c.book_diffs) {
        // d.raw_book_diff is { new: { sz } } | { update: { origSz, newSz } } | "remove"
        console.log("  ", d.side, d.oid, d.px, JSON.stringify(d.raw_book_diff));
      }
    }
  });
  ```

  ```python Python theme={null}
  import asyncio, json, os
  import websockets

  async def main():
      uri = f"wss://hypercore.goldrushdata.com/ws?key={os.environ['GOLDRUSH_API_KEY']}"
      async with websockets.connect(uri, max_size=None) as ws:
          await ws.send(json.dumps({
              "method": "subscribe",
              "subscription": {"type": "l4BookUpdates", "coin": "BTC"},
          }))
          async for raw in ws:
              msg = json.loads(raw)
              if msg.get("channel") != "l4BookUpdates":
                  continue
              upd = msg["data"]["Updates"]
              for c in upd["book_diffs"]:
                  print(upd["block_height"], upd["epoch"], c["coin"], "seq", c["prev_seq"], "->", c["seq"])
                  for d in c["book_diffs"]:
                      print("  ", d.get("side"), d["oid"], d["px"], d["raw_book_diff"])

  asyncio.run(main())
  ```
</CodeGroup>

## Unsubscribe

Send the same `subscription` body with `method: "unsubscribe"`:

```json theme={null}
{
  "method": "unsubscribe",
  "subscription": { "type": "l4BookUpdates", "coin": "BTC" }
}
```

<Note>
  Unsubscribe matches subscriptions by **exact body**. A subscription created with `coin: ["BTC", "ETH"]` is a different subscription from one created with `coin: "BTC"`. To narrow a multi-coin set, unsubscribe the original `coin` array in full, then resubscribe with the smaller list.
</Note>

## Streamed messages

Every message has `channel: "l4BookUpdates"` and a `data` payload containing an `Updates` batch (this stream never emits a `Snapshot`). An `Updates` batch is emitted on each HyperCore block where the book for at least one subscribed coin changed. Each per-coin entry carries the block's `order_statuses` (order lifecycle events) and `book_diffs` (per-order changes) for that coin, plus its own `seq`/`prev_seq`.

```json theme={null}
{
  "channel": "l4BookUpdates",
  "data": {
    "Updates": {
      "time": 1778865761768,
      "block_height": 997719813,
      "epoch": "466bf60f-bb7a-4591-89a3-a2660234fef5",
      "cursor": "997719813:1778865761768",
      "book_diffs": [
        {
          "coin": "BTC",
          "seq": 4229978,
          "prev_seq": 4229977,
          "order_statuses": [
            {
              "time": "2026-05-15T17:22:41.768005701",
              "user": "0x31ca8395cf837de08b24da3f660e77761dfb974b",
              "status": "open",
              "order": {
                "user": null,
                "coin": "BTC",
                "side": "B",
                "limitPx": "79242.0",
                "sz": "0.00867",
                "oid": 427632416336,
                "timestamp": 1778865761768,
                "triggerCondition": "N/A",
                "isTrigger": false,
                "triggerPx": "0.0",
                "isPositionTpsl": false,
                "reduceOnly": false,
                "orderType": "Limit",
                "tif": "Alo",
                "cloid": null
              }
            }
          ],
          "book_diffs": [
            {
              "user": "0x31ca8395cf837de08b24da3f660e77761dfb974b",
              "oid": 427632416336,
              "side": "B",
              "px": "79242.0",
              "coin": "BTC",
              "raw_book_diff": { "new": { "sz": "0.00867" } }
            }
          ]
        }
      ]
    }
  }
}
```

The `raw_book_diff` descriptor is one of:

* `{ "new": { "sz": "<size>" } }` — a newly resting order.
* `{ "update": { "origSz": "<old>", "newSz": "<new>" } }` — a resize (e.g. partial fill).
* `"remove"` — the order left the book (filled or cancelled).

## Bootstrapping & gap detection

`l4BookUpdates` is diff-only, so you build the order-level book yourself by seeding from a snapshot and applying diffs in order — the same mechanism a reference L4 client uses:

1. **Subscribe** to `l4BookUpdates` for your coin(s) and start **buffering** the incoming `Updates`.
2. **Fetch a snapshot** for each coin from the REST [`l4BookSnapshot`](/docs/api-reference/hyperliquid-info/l4-book-snapshot) info method. Note its `height`, `epoch`, and per-coin `seq`.
3. **Discard already-applied diffs.** Drop any buffered batch whose `block_height` is **≤** the snapshot's `height` (the snapshot already includes those blocks). Equivalently, per coin, drop any diff whose `seq` **≤** the snapshot's `seq`.
4. **Verify the epoch.** Every remaining diff's `epoch` must equal the snapshot's `epoch`. A different `epoch` means the stream reset after your snapshot — re-fetch the snapshot and restart.
5. **Apply in order.** For each coin, the first diff you apply should have `prev_seq == snapshot.seq`. For each `raw_book_diff`, insert (`new`), resize (`update`), or delete (`remove`) the order by `oid`.
6. **Ongoing gap detection.** Per coin, each diff's `prev_seq` must equal the last `seq` you applied for that coin. On a mismatch (a dropped message) or an `epoch` change (a server reset), re-bootstrap that coin from step 2.

<Tip>
  The `seq`/`prev_seq` are **per coin**, tracked within the current `epoch`. Compare them per coin, not globally across the batch.
</Tip>

## Response fields

<ResponseField name="channel" type="string">
  Always `"l4BookUpdates"`.
</ResponseField>

<ResponseField name="data" type="object">
  Contains an `Updates` batch.

  <Expandable title="Updates (per-block diffs)">
    <ResponseField name="time" type="int">HyperCore block timestamp in milliseconds.</ResponseField>
    <ResponseField name="block_height" type="int">HyperCore block height these diffs were produced at. Align against the snapshot's `height`.</ResponseField>

    <ResponseField name="epoch" type="string">
      Server generation (UUID). Regenerated when the stream resets (e.g. a server restart); a new `epoch` means your snapshot and buffered diffs are stale, so re-bootstrap. Matches the `epoch` in the [`l4BookSnapshot`](/docs/api-reference/hyperliquid-info/l4-book-snapshot) response.
    </ResponseField>

    <ResponseField name="cursor" type="string">
      A per-batch position marker, formatted `"<block_height>:<time>"`. To recover from a disconnect, re-bootstrap from a fresh `l4BookSnapshot` (see [Bootstrapping & gap detection](#bootstrapping--gap-detection)) — the stream does not replay from this value.
    </ResponseField>

    <ResponseField name="book_diffs" type="array<object>">
      Per-coin entries. One entry per subscribed coin whose book changed at this block.

      <Expandable title="per-coin entry">
        <ResponseField name="coin" type="string">Asset symbol this entry applies to.</ResponseField>
        <ResponseField name="seq" type="int">Per-coin monotonic sequence for this entry, within the current `epoch`.</ResponseField>
        <ResponseField name="prev_seq" type="int">The `seq` of the previous entry for this coin. Must equal the last `seq` you applied for the coin (or the snapshot's `seq` for the first entry after a bootstrap). A mismatch signals a gap.</ResponseField>

        <ResponseField name="order_statuses" type="array<object>">
          Order-lifecycle events for this coin at this block.

          <Expandable title="order_status fields">
            <ResponseField name="time" type="string">ISO-8601 timestamp with nanosecond precision.</ResponseField>
            <ResponseField name="user" type="string">Wallet address that owns the order.</ResponseField>
            <ResponseField name="status" type="string">Lifecycle status (e.g. `"open"`).</ResponseField>
            <ResponseField name="order" type="Order">The order, in the same shape as an [`l4Book`](/docs/api-reference/hyperliquid-websocket/l4-book) snapshot entry. `user` inside this nested object is `null` because it duplicates the parent `user`.</ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="book_diffs" type="array<object>">
          Per-order book changes for this coin at this block.

          <Expandable title="book_diff fields">
            <ResponseField name="user" type="string">Wallet address that owns the order.</ResponseField>
            <ResponseField name="oid" type="int">Order id the diff applies to.</ResponseField>
            <ResponseField name="side" type="string">`"B"` for bid, `"A"` for ask.</ResponseField>
            <ResponseField name="px" type="string">Price the order rests at (decimal string).</ResponseField>
            <ResponseField name="coin" type="string">Asset symbol.</ResponseField>
            <ResponseField name="raw_book_diff" type="object | string">The change descriptor: `{ "new": { "sz" } }`, `{ "update": { "origSz", "newSz" } }`, or the string `"remove"`.</ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Related endpoints

<CardGroup cols={2}>
  <Card title="l4Book" href="/docs/api-reference/hyperliquid-websocket/l4-book">subscribe to GoldRush's order-level Hyperliquid book - initial snapshot of every resting order plus per-block diffs with full metadata.</Card>
  <Card title="l4BookSnapshot" href="/docs/api-reference/hyperliquid-info/l4-book-snapshot">fetch a full order-level (L4) book snapshot for one coin, with height, epoch, and seq to bootstrap the l4BookUpdates stream.</Card>
  <Card title="l2BookDiff2" href="/docs/api-reference/hyperliquid-websocket/l2-book-diff2">subscribe to a diff-only L2 order book stream with per-coin sequence numbers and a server epoch for gap detection.</Card>
</CardGroup>

See all Hyperliquid endpoints: [Overview hub](/docs/goldrush-hyperliquid/overview) · [WebSocket API reference](/docs/goldrush-hyperliquid/websocket-api/overview)
