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

# WebSocket overview

> One endpoint, two families of streams: how to connect, authenticate, keep the connection alive and choose the right subscription.

Everything on this page was checked against production on 19 September 2026.

## One endpoint

```text theme={null}
wss://api.hypedexer.com/ws
```

A global load balancer routes the connection to the nearest region (Frankfurt or Tokyo). There is no separate host per region and no separate host per stream family.

## Authentication

Authenticate during the handshake, with the same API key you use for REST:

| Method | Example |
| - | - |
| Header | `X-API-Key: <key>` |
| Header | `Authorization: Bearer <key>` |
| Query parameter | `wss://api.hypedexer.com/ws?api_key=<key>` |

A handshake without a valid key is refused with HTTP `401` before the connection opens. Browsers cannot set handshake headers, so use the query parameter there and a header everywhere else.

<CodeGroup>
  ```bash wscat theme={null}
  wscat -H "X-API-Key: $HYPEDEXER_API_KEY" -c wss://api.hypedexer.com/ws
  # then paste:
  # {"method":"subscribe","subscription":{"type":"bbo","coin":"BTC"}}
  ```

  ```javascript Node.js theme={null}
  import WebSocket from "ws";

  const ws = new WebSocket("wss://api.hypedexer.com/ws", {
    headers: { "X-API-Key": process.env.HYPEDEXER_API_KEY },
  });

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

  ws.on("message", (raw) => {
    const frame = JSON.parse(raw);
    if (frame.type === "ping") {
      ws.send(JSON.stringify({ method: "pong" }));   // keeps the connection open
      return;
    }
    console.log(frame);
  });
  ```
</CodeGroup>

On connect, the server sends:

```json theme={null}
{"type": "connected"}
```

## Heartbeat, and the 180-second cutoff

The server sends a JSON ping every 60 seconds:

```json theme={null}
{"type": "ping", "ts": 1789800828496}
```

Answer each one with `{"method": "pong"}`. A connection that never answers is closed after about 180 seconds with close code `4000` and the reason `timeout`. This is measured behaviour: a silent client was cut at 182 seconds, a client that replied stayed open.

## Two families of streams on the same socket

Both families are subscribed the same way, `{"method": "subscribe", "subscription": {"type": "<name>"}}`, but they differ in what a data frame looks like. Check the `type`/`channel` key rather than assuming.

| | Live data (mirrored from Hyperliquid) | Indexed data (built by our indexer) |
| - | - | - |
| Subscriptions | `allMids`, `trades`, `candle`, `allCandles`, `bbo`, `l2Book`, `l4Book`, `l4BookUpdates`, `activeAssetCtx`, `allActiveAssetCtx`, `fundingRates`, `setOracleUpdates`, `userFills`, `allFills`, `orderUpdates`, `allTwapStatusUpdates`, `userEvents`, `userFundings`, `userNonFundingLedgerUpdates`, `webData2` | `liquidations` |
| Acknowledgement | `{"channel":"subscriptionResponse","data":{"method":"subscribe","subscription":{...}}}`; `l2Book`, `l4Book`, `l4BookUpdates` and `bbo` answer `{"type":"subscriptionUpdate","subscription":{...},"active":true}` | `{"channel":"subscriptionResponse","data":{"method":"subscribe","subscription":{...}}}`, then one `liquidation` frame with the recent cached events |
| Data frame | `{"channel":"<name>","data":{...}}` | `{"type":"liquidation","count":N,"data":[...]}` |
| Documented in | [Live data](/live-data/websocket) | this section |

The optional `?mode=mirror` parameter restricts a connection to the live family. It is not required: without it, the same socket serves both, which is what the examples above do.

<Note>
  Three streams described in this section, `completed_trades`, `fills_spot` and `recent_activity`, are refused by the public endpoint today with `{"type":"error","message":"Unsupported subscription: ..."}`. Use the REST equivalents ([completed trades](/api-reference/completed-trades/list-completed-trades), [spot fills](/api-reference/fills/list-spot-fills-only)) and [contact us](https://t.me/EnigmaValidatorCommunity/1) if you need them pushed.
</Note>

## Managing subscriptions

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

One socket carries many subscriptions. An unknown name is answered with an error frame and the connection stays open:

```json theme={null}
{"type": "error", "message": "Unsupported subscription: <name>"}
```

## Billing and per-plan limits

Ten delivered events count as one credit, pooled with your REST credits.

| Plan | Connections | Subscriptions |
| - | -: | -: |
| Starter | 4 | 5 |
| Growth | 10 | 20 |
| Scale | 30 | 60 |
| Enterprise | Custom | Custom |

The same numbers are in [Rate Limits & Pricing](/guides/rate-limits-and-quotas), which is the reference if the two ever disagree.

## Reconnecting

* Reconnect with exponential backoff and jitter; do not reconnect in a tight loop after a `401`, the key will not become valid by retrying.
* Re-send your subscriptions after every reconnect, the server keeps no state for you.
* There is no replay: a stream resumes at the present moment. For anything you cannot afford to miss, backfill the gap over REST using the timestamps of the last frame you processed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.