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

# l2Book | Hyperliquid WebSocket API

> Hyperliquid l2Book: subscribe to real-time L2 order book snapshots for all Hyperliquid assets over WebSocket.

<CardGroup cols={2}>
  <Card title="Credit Cost"> 0.5 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).
</Tip>

<Info>
  * Wire-compatible with `wss://api.hyperliquid.xyz/ws` `l2Book` subscriptions - same channel name, same `levels` shape.
  * `coin` is optional on GoldRush. Omit it to stream the entire L2 order book across every asset on a single subscription. The public Hyperliquid API requires `coin` and locks each subscription to one asset at a time.
  * 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 `l2Book` 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 `50` **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 `"l2Book"`.</ResponseField>
    <ResponseField name="coin" type="string">Asset symbol - e.g. `"BTC"`, `"ETH"`, `"@107"` for spot pairs. For HIP-3 markets, include the deployer prefix. Omit to receive snapshots for **all perp assets** (the default; see `marketTypes` to include spot or outcome markets).</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>

    <ResponseField name="nSigFigs" type="int">Significant figures used for price aggregation. One of `2`, `3`, `4`, `5`, or `null` for full precision. Defaults to `null`.</ResponseField>
    <ResponseField name="mantissa" type="int">When `nSigFigs` is 5, controls the mantissa rounding. One of `1`, `2`, or `5`. Not allowed for other `nSigFigs` values.</ResponseField>
  </Expandable>
</ParamField>

### Example

Pick the subscription shape that matches the coverage you want:

| Subscribe with                                    | What you receive                                                           |
| ------------------------------------------------- | -------------------------------------------------------------------------- |
| `{"type":"l2Book","coin":"BTC"}`                  | L2 snapshots for **BTC only**                                              |
| `{"type":"l2Book"}`                               | L2 snapshots for **every perp coin** (default: `marketTypes: ["perp"]`)    |
| `{"type":"l2Book","marketTypes":["spot"]}`        | L2 snapshots for **every spot coin**                                       |
| `{"type":"l2Book","marketTypes":["outcome"]}`     | L2 snapshots for **every HIP-4 outcome market**                            |
| `{"type":"l2Book","marketTypes":["perp","spot"]}` | L2 snapshots for **perps + spot**                                          |
| `{"type":"l2Book","marketTypes":["*"]}`           | L2 snapshots 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":"l2Book","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: "l2Book", coin: "BTC" },
    }));
  });

  ws.on("message", (raw) => {
    const msg = JSON.parse(raw.toString());
    if (msg.channel === "l2Book") {
      const { coin, time, levels: [bids, asks] } = msg.data;
      console.log(coin, time, "top bid:", bids[0], "top ask:", asks[0]);
    }
  });
  ```

  ```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": "l2Book", "coin": "BTC"},
          }))
          async for raw in ws:
              msg = json.loads(raw)
              if msg.get("channel") == "l2Book":
                  print(msg["data"]["coin"], msg["data"]["time"], msg["data"]["levels"][0][:1])

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

## Unsubscribe

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

```json theme={null}
{
  "method": "unsubscribe",
  "subscription": { "type": "l2Book", "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"` 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": "l2Book", "coin": ["BTC", "ETH"] } }
  { "method": "subscribe",   "subscription": { "type": "l2Book", "coin": ["BTC"] } }
  ```
</Note>

## Streamed message

Each message has `channel: "l2Book"` and a `data` payload with the current book snapshot for the subscribed coin.

```json theme={null}
{
  "channel": "l2Book",
  "data": {
    "coin": "BTC",
    "time": 1762450000123,
    "block_height": 996629014,
    "levels": [
      [
        { "px": "68210.0", "sz": "1.2345", "n": 3 },
        { "px": "68209.5", "sz": "4.5670", "n": 5 },
        { "px": "68209.0", "sz": "2.1100", "n": 2 }
      ],
      [
        { "px": "68215.0", "sz": "0.8900", "n": 2 },
        { "px": "68215.5", "sz": "3.4500", "n": 4 },
        { "px": "68216.0", "sz": "1.2300", "n": 1 }
      ]
    ]
  }
}
```

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

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <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<object>>">
      Tuple `[bids, asks]`. Each side is an array of price levels 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>
</ResponseField>

## Related endpoints

<CardGroup cols={2}>
  <Card title="l2BookDiff" href="/api-reference/hyperliquid-websocket/l2-book-diff">Subscribe to real-time L2 order book (initial snapshot + diffs) for all Hyperliquid assets over WebSocket.</Card>
</CardGroup>

*Last reviewed: 2026-06-16*
