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

# l2BookDiff | Hyperliquid WebSocket API

> Hyperliquid l2BookDiff: subscribe to real-time L2 order book (initial snapshot + diffs) for all Hyperliquid assets over WebSocket.

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

<Tip>
  Estimate your monthly cost for this API using the [Pricing Calculator](/pricing-calculator?endpoint=%2Fapi-reference%2Fhyperliquid-websocket%2Fl2-book-diff).
</Tip>

<Info>
  * GoldRush-native. `l2BookDiff` is not exposed on `wss://api.hyperliquid.xyz/ws`. Pointing a client at the public endpoint with this subscription type will fail.
  * Snapshot, then diffs. The first message is always a `Snapshot`; every message thereafter is an `Updates`. Clients must seed local book state from the snapshot and apply diffs from there. On reconnect, drop local state and re-seed from the next snapshot.
  * `coin` is optional. Omit it to stream the entire L2 order book across every asset on a single subscription.
  * When `coin` is omitted, the optional `marketTypes` filter selects which market families to include. It defaults to `["perp"]` only.
  * Pass `"marketTypes": ["spot"]`, or `"marketTypes":["outcome"]`, or a mix (e.g. `"marketTypes": ["perp","spot"]`), or use the wildcard `"marketTypes": ["*"]` to opt into spot, perps, outcome, and any future market types.
  * No 1000-subscription-per-IP cap - multiplex hundreds of `l2BookDiff` subscriptions on a single connection.
  * For OHLCV candles instead of raw book state, use the <a href="https://goldrush.dev/docs/api-reference/streaming-api/subscriptions/ohlcv-pairs-stream" target="_blank" rel="noopener noreferrer">Streaming API OHLCV streams</a>.
</Info>

**Note:** When `coin` is omitted, a credit rate of `10` **credits per minute subscribed** is applied.

## 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 `"l2BookDiff"`.</ResponseField>

    <ResponseField name="coin" type="string | string[]">
      Asset filter. Accepts three shapes:

      * **String** - a single asset symbol (e.g. `"HYPE"`, `"BTC"`, `"@107"` for spot pairs). For HIP-3 markets, include the deployer prefix.
      * **Array of strings** - a fixed list of asset symbols (e.g. `["HYPE", "BTC", "ETH"]`). Each listed coin gets its own initial `Snapshot`; subsequent `Updates` may bundle diffs for any subset of the list per block.
      * **Omitted** - wildcard. Streams the full L2 book across **every perp asset** by default on one subscription (see `marketTypes` to include spot or outcome markets). The server emits one `Snapshot` per live coin, then per-block `Updates` covering only the coins that changed.
    </ResponseField>

    <ResponseField name="marketTypes" type="string[]">
      Optional. Selects which market families a wildcard subscription includes. **Only valid when `coin` is omitted.** **Defaults to `["perp"]`** - when omitted, only perp markets stream. Spot and outcome markets require explicit opt-in.

      Accepted values:

      * `"perp"` *(default)* - vanilla perps and HIP-3 deployer-perps (e.g. `BTC`, `ETH`, `HYPE`, `SOL`, `cash`, `abcd`, `USA500`).
      * `"spot"` - `@<index>` spot markets and legacy spot pairs (e.g. `@1`, `@107` for HYPE spot, `PURR/USDC`). Not included by default.
      * `"outcome"` - HIP-4 prediction-market outcomes (e.g. `#700`, `#710`, `#741`). Not included by default.
      * `"*"` - every current type (`"perp"` + `"spot"` + `"outcome"`) and auto-opt-in to any future types the server adds.

      Mix and match in one subscription, e.g. `["perp", "outcome"]`. A second `subscribe` with a different `marketTypes` value **replaces** the previous filter rather than coexisting with it.
    </ResponseField>
  </Expandable>
</ParamField>

### Example

Pick the subscription shape that matches the coverage you want. Every subscription starts with one `Snapshot` per coin in scope, then per-block `Updates` carrying only changed levels:

| Subscribe with                                        | What you receive                                                               |
| ----------------------------------------------------- | ------------------------------------------------------------------------------ |
| `{"type":"l2BookDiff","coin":"HYPE"}`                 | Snapshot + diffs for **HYPE only**                                             |
| `{"type":"l2BookDiff","coin":["HYPE","BTC","ETH"]}`   | Snapshot + diffs for **a fixed list of coins**                                 |
| `{"type":"l2BookDiff"}`                               | Snapshot + diffs for **every perp coin** (default: `marketTypes: ["perp"]`)    |
| `{"type":"l2BookDiff","marketTypes":["spot"]}`        | Snapshot + diffs for **every spot coin**                                       |
| `{"type":"l2BookDiff","marketTypes":["outcome"]}`     | Snapshot + diffs for **every HIP-4 outcome market**                            |
| `{"type":"l2BookDiff","marketTypes":["perp","spot"]}` | Snapshot + diffs for **perps + spot**                                          |
| `{"type":"l2BookDiff","marketTypes":["*"]}`           | Snapshot + diffs for **every coin** (perp + spot + outcome, plus future types) |

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

  > {"method":"subscribe","subscription":{"type":"l2BookDiff","coin":"HYPE"}}
  ```

  ```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: "l2BookDiff", coin: "HYPE" },
    }));
  });

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

    if (msg.data.Snapshot) {
      const { coin, time, block_height, levels: [bids, asks] } = msg.data.Snapshot;
      console.log("snapshot", coin, time, block_height, "bids:", bids.length, "asks:", asks.length);
    } else if (msg.data.Updates) {
      const { time, block_height, book_diffs } = msg.data.Updates;
      console.log("updates", time, block_height, "coins:", book_diffs.length);
    }
  });
  ```

  ```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) as ws:
          await ws.send(json.dumps({
              "method": "subscribe",
              "subscription": {"type": "l2BookDiff", "coin": "HYPE"},
          }))
          async for raw in ws:
              msg = json.loads(raw)
              if msg.get("channel") != "l2BookDiff":
                  continue
              data = msg["data"]
              if "Snapshot" in data:
                  snap = data["Snapshot"]
                  bids, asks = snap["levels"]
                  print("snapshot", snap["coin"], snap["time"], snap["block_height"], "bids:", len(bids), "asks:", len(asks))
              elif "Updates" in data:
                  upd = data["Updates"]
                  print("updates", upd["time"], upd["block_height"], "coins:", len(upd["book_diffs"]))

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

## Unsubscribe

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

```json theme={null}
{
  "method": "unsubscribe",
  "subscription": { "type": "l2BookDiff", "coin": "HYPE" }
}
```

<Note>
  Unsubscribe matches subscriptions by **exact body**. A subscription created with `coin: ["BTC", "ETH"]` is a different subscription from one created with `coin: "BTC"` or `coin: "ETH"`.

  **You cannot unsubscribe a partial set of coins from an existing multi-coin subscription.** To narrow the set, unsubscribe the original `coin` array in full, then resubscribe with the smaller list:

  ```json theme={null}
  // Drop ETH from a ["BTC","ETH"] subscription:
  { "method": "unsubscribe", "subscription": { "type": "l2BookDiff", "coin": ["BTC", "ETH"] } }
  { "method": "subscribe",   "subscription": { "type": "l2BookDiff", "coin": ["BTC"] } }
  ```
</Note>

## Streamed messages

Every message has `channel: "l2BookDiff"`. The `data` payload contains **exactly one** of two variants: a `Snapshot` (emitted once per subscribed coin, immediately after subscribe) or an `Updates` (emitted on each subsequent HyperCore block where the book for at least one subscribed coin changed).

### Initial snapshot

After subscribe, the server emits one `Snapshot` message per coin currently in scope. For a single-coin subscription that is one message; for a list or wildcard subscription that is one message per asset. Each entry in `levels[0]` (bids) and `levels[1]` (asks) is an aggregated price level, sorted best-first.

```json theme={null}
{
  "channel": "l2BookDiff",
  "data": {
    "Snapshot": {
      "coin": "HYPE",
      "time": 1779220051027,
      "block_height": 1002862373,
      "levels": [
        [
          { "px": "48.601", "sz": "51.26", "n": 1 }
        ],
        [
          { "px": "48.614", "sz": "12.34", "n": 1 }
        ]
      ]
    }
  }
}
```

### Incremental updates

Subsequent messages carry only the levels that changed since the previous block, grouped by coin. A level entry with `sz: "0"` and `n: 0` means **the level at that price has been removed**; any other entry replaces the current state at that `px` with the new `{sz, n}`.

```json theme={null}
{
  "channel": "l2BookDiff",
  "data": {
    "Updates": {
      "time": 1779220051224,
      "block_height": 1002862374,
      "book_diffs": [
        {
          "coin": "HYPE",
          "levels": [
            [
              { "px": "48.601", "sz": "60.00", "n": 2 }
            ],
            [
              { "px": "48.614", "sz": "0", "n": 0 }
            ]
          ]
        }
      ]
    }
  }
}
```

### Response fields

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

<ResponseField name="data" type="object">
  Contains exactly one of `Snapshot` or `Updates`.

  <Expandable title="Snapshot (one per subscribed coin, emitted after subscribe)">
    <ResponseField name="coin" type="string">Asset symbol the snapshot belongs to.</ResponseField>
    <ResponseField name="time" type="int">HyperCore block timestamp in milliseconds.</ResponseField>
    <ResponseField name="block_height" type="int">HyperCore block height the snapshot was taken at.</ResponseField>

    <ResponseField name="levels" type="array<array<Level>>">
      Tuple `[bids, asks]`. Each side is an array of aggregated **Level** objects in best-first order.

      <Expandable title="level properties">
        <ResponseField name="px" type="string">Price for this level (decimal string).</ResponseField>
        <ResponseField name="sz" type="string">Aggregate size resting at this level (decimal string, base units).</ResponseField>
        <ResponseField name="n" type="int">Number of orders aggregated into this level.</ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>

  <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.</ResponseField>

    <ResponseField name="book_diffs" type="array<object>">
      Per-coin lists of changed price levels. One entry per coin that had changes at this block.

      <Expandable title="book_diff fields">
        <ResponseField name="coin" type="string">Asset symbol the diff applies to.</ResponseField>

        <ResponseField name="levels" type="array<array<Level>>">
          Tuple `[changed_bids, changed_asks]`. Each entry replaces the current state at its `px`. An entry with `sz: "0"` and `n: 0` removes the level at that price.

          <Expandable title="level properties">
            <ResponseField name="px" type="string">Price for this level (decimal string).</ResponseField>
            <ResponseField name="sz" type="string">New aggregate size at this level (decimal string). `"0"` means the level is removed.</ResponseField>
            <ResponseField name="n" type="int">New number of orders aggregated into this level. `0` means the level is removed.</ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## Related endpoints

<CardGroup cols={2}>
  <Card title="l2Book" href="/api-reference/hyperliquid-websocket/l2-book">Subscribe to real-time L2 order book snapshots for all Hyperliquid assets over WebSocket.</Card>
</CardGroup>

*Last reviewed: 2026-06-16*
