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

# WebSocket connection and authentication

> Authenticate and connect to the 0xArchive WebSocket, then keep the connection alive, reconnect safely, and handle streaming failures.

Open an authenticated socket to `wss://api.0xarchive.io/ws`, confirm each command, and reconnect without losing your subscriptions. The socket uses your 0xArchive API key, sent in the opening handshake as `Authorization: Bearer $OXARCHIVE_API_KEY` instead of the REST `X-API-Key` header. To create, store or rotate a key, see [API keys and credential safety](/authentication).

<Warning>
  Keep WebSocket credentials on the server. Browser WebSocket clients cannot set a custom `Authorization` header, so browser apps should connect through your backend. Never expose a real API key in browser code, public prompts, logs, screenshots, or shared notebooks.
</Warning>

<Steps>
  <Step title="Open the socket">
    ```javascript theme={"theme":"github-dark"}
    import WebSocket from "ws";

    const apiKey = process.env.OXARCHIVE_API_KEY;
    if (!apiKey) throw new Error("Set OXARCHIVE_API_KEY");

    const ws = new WebSocket("wss://api.0xarchive.io/ws", {
      headers: { Authorization: `Bearer ${apiKey}` }
    });
    ```

    Use a server-side WebSocket library that can set request headers, such as `ws` in Node.js.
  </Step>

  <Step title="Send commands after the transport opens">
    ```javascript theme={"theme":"github-dark"}
    ws.onopen = () => {
      // `open` confirms transport and authentication only.
      ws.send(JSON.stringify({ op: "subscribe", channel: "orderbook", symbol: "BTC" }));
    };
    ```
  </Step>

  <Step title="Confirm each command">
    ```javascript theme={"theme":"github-dark"}
    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      if (message.type === "subscribed") console.log("subscription accepted");
      if (message.type === "error") console.error(message.error_code, message.message);
    };
    ```

    A successful handshake is not a successful subscription or replay. Wait for `subscribed` after `subscribe`, or `replay_started` after `replay`.
  </Step>

  <Step title="Reconnect with backoff">
    After a close or network failure, reconnect with capped backoff. Resubscribe only after the new socket opens, then rebuild any state that depended on the old stream.
  </Step>
</Steps>

## Message contract version

Add `?version=2026-10-01` to the socket URL to receive the current message contract: `subscribed` and `replay_started` then echo the version, and Lighter replay rows use the live shapes. See [Versioning](/conventions#versioning). Every `error` message carries a stable `error_code`; the codes are listed on [Errors](/errors#websocket-errors).

## Keep the connection alive

The server sends a WebSocket ping frame every 30 seconds and tracks pong activity. WebSocket libraries normally answer ping frames automatically. A connection with active subscriptions can be closed after 60 seconds without activity; a connection with no subscriptions has a 300-second idle window.

You can also send the application command `{"op":"ping"}`, which receives `{"type":"pong"}`. The round trip it measures is your own client's observation, not a published platform latency figure. [Keep-alive](/websocket/keep-alive) has the ping loop and the full reconnect sequence.

## Track connection state

Keep one state object per stream so a reconnect restores exactly what was running, and so your logs describe the same stream support sees.

```javascript theme={"theme":"github-dark"}
const streamState = {
  status: "connecting",
  activeSubscriptions: new Map(),
  lastMessageAt: null,
  retryCount: 0,
  gapEvents: [],
  unsafeSymbols: new Set()
};
```

Update it in `onopen`, `onmessage`, `onerror`, and `onclose`:

* **On close:** clear the old socket's timers, set the status to `reconnecting`, and open a new socket with capped exponential backoff and jitter. Never reconnect in a tight loop.
* **On open:** restore subscriptions from `activeSubscriptions`, not from assumptions about the old stream.
* **On a gap:** add the symbol to `unsafeSymbols` until the client rebuilds from a snapshot or replay checkpoint.
* **On every message:** parse defensively. Expect acknowledgements, snapshots, updates, replay events, pongs, gap events, and errors. Keep the `message` text of each `error` and any request or correlation ID with the connection state.

Log the close code, reason, retry count, and restored subscriptions on every reconnect. That record separates normal network churn from a broken consumer or a bad key.

## Handle failures

| Failure | Client response |
| - | - |
| Auth or command error | Stop widening the stream and check the key, command, channel, and symbol |
| Network close | Reconnect with capped backoff and restore tracked subscriptions |
| Repeated close loop | Stop after the retry budget and alert the job's owner |
| Gap event | Mark derived local state incomplete until it is rebuilt |
| Consumer backlog | Reduce channels, slow standard replay, or move heavy work off the message callback |

## Next step

Pick the channel and symbol for your first subscription in [WebSocket channels](/websocket/channels).


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