# Hyperliquid WebSocket

> A direct order-book feed from our own Hyperliquid node over WebSocket — up to 1000 levels per side, pushed on every block. Native Hyperliquid protocol, so existing client code works after swapping the URL.

A direct feed of the Hyperliquid order book from our own node, over one WebSocket connection. It speaks the **native Hyperliquid WebSocket protocol**: the same subscribe envelope, the same field names, the same `channel` / `data` response shape. Client code written against the Hyperliquid documentation works here after you change the URL and add the auth header — see [where this differs](#where-this-differs-from-the-public-api) for the short list of exceptions.

<Note>
**One flat tier.** Streaming is billed monthly with no per-message fees. See [Pricing](https://supanode.xyz/docs/hyperliquid/pricing).
</Note>

## Why a node feed instead of the public API

The public Hyperliquid API rebuilds the book on a timer. Our feed pushes a frame **on every book change**, which on Hyperliquid means on every block.

| | Supanode | Public Hyperliquid API |
|---|---|---|
| Depth per side | up to **1000 levels** | 20 levels |
| Frames per 90 seconds, BTC | **1049** | 18 |
| Median gap between frames | **61 ms** — the block time | 5423 ms |

Measured on 9 September 2026, both feeds read at the same time from the same machine.

## What makes this feed different

- **Depth to 1000 levels.** The public API gives 20. Providers running the same order-book software stop at 100. We serve up to 1000 per side.
- **Block-rate updates.** A frame per book change, not per timer tick. The median gap is the network's own block time.
- **The whole set of streams on one connection.** The full book, incremental updates, best bid and offer, trades, the per-order book with owner addresses, and order statuses.

## Endpoint

```
wss://toy.hl.supanode.xyz:48080/ws
```

The node runs in **Tokyo**. The scheme is `wss://` — plain `ws://` and `http://` do not work.

## Authentication

Send your key as an HTTP header on the WebSocket handshake:

```
x-token: YOUR_KEY
```

The header name is not case sensitive, so `X-Token` also works. There is no login call and no query parameter.

| Handshake | Result |
|---|---|
| Valid key in `x-token` | `101 Switching Protocols` |
| No key | `401 Unauthorized` |
| Key that is not yours | `403 Forbidden` |

<Warning>
**A browser cannot connect directly.** The browser WebSocket API cannot set request headers, so the key cannot be sent from page JavaScript. Connect from your backend and pass the data to the browser yourself. Query-parameter keys (`?token=`, `?apikey=`), `Authorization: Bearer` and `x-api-key` are not accepted.
</Warning>

## How a subscription works

One connection carries as many subscriptions as you need. Each one is a small JSON message.

**Subscribe:**

```json
{ "method": "subscribe", "subscription": { "type": "l2Book", "coin": "BTC" } }
```

The server answers with a `subscriptionResponse`, then streams frames until you unsubscribe or the connection closes:

```json
{ "channel": "subscriptionResponse",
  "data": { "method": "subscribe", "subscription": {"coin":"BTC","type":"l2Book"} } }
```

**Unsubscribe:**

```json
{ "method": "unsubscribe", "subscription": { "type": "l2Book", "coin": "BTC" } }
```

<Warning>
**Repeat the subscription object exactly.** Unsubscribe matches on the whole object, including `nLevels`. If you subscribed with `"nLevels": 100`, you must unsubscribe with `"nLevels": 100` as well. An object that does not match is ignored and the stream keeps running.
</Warning>

<Tip>
**Check the acknowledgement on `coin` and `type`, not on the whole object.** The `subscriptionResponse` echoes the fields the server recognised. If you sent extra fields, they will not come back, even though the stream starts normally. A client library that compares the answer to the request field by field will read that as a failed subscription when nothing is wrong.
</Tip>

## Where this differs from the public API

The protocol is theirs, so client code written against the Hyperliquid documentation works here — with four differences worth knowing before you start.

| | Supanode | Public Hyperliquid API |
|---|---|---|
| Authentication | `x-token` header on the handshake | none |
| Depth, `nLevels` | up to 1000 | up to 20 |
| Incremental book | `l2Diff` channel | not available |
| `bbo` frame | two fields, `bid` and `ask` | one field `bbo`, holding a list of two |
| `l2Book` frame | carries `nLevels` | no such field |
| Unsubscribe | the subscription object must match exactly, including `nLevels` | matches without it |

## Naming a market

- **Perpetuals** use the plain symbol: `BTC`, `ETH`, `SOL`, `HYPE`.
- **Spot pairs** use `@<index>` from `spotMeta`, for example `@107`.

## Keep-alive

The server does not ping you. Two ways to keep the connection alive, both supported:

- standard WebSocket protocol ping frames — the server answers with pong;
- an application message `{"method":"ping"}` — the server answers `{"channel":"pong"}`.

A connection with no subscriptions is not closed for being idle.

## After a reconnect

Subscriptions are not restored. Send them again on the new connection.

## Next steps

<CardGroup cols={3}>
  <Card title="Streams" icon="list" href="https://supanode.xyz/docs/hyperliquid/websocket/whats-available">
    Every stream, its subscribe message and a real frame.
  </Card>
  <Card title="Limits" icon="gauge" href="https://supanode.xyz/docs/hyperliquid/websocket/limits">
    What is capped today, and what is still being built.
  </Card>
  <Card title="Examples" icon="code" href="https://supanode.xyz/docs/hyperliquid/websocket/examples">
    Working code: connect, build the book, survive a drop.
  </Card>
</CardGroup>

## External references

- [Hyperliquid WebSocket subscriptions](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions) — the protocol this feed implements.
