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

# SDK WebSocket usage

> Use 0xArchive SDK helpers for WebSocket live subscriptions, historical replay, reconnect behavior, and order-book reconstruction.

Stream and replay market data from the SDK WebSocket clients. They handle the connection, reconnects, subscription state, and message typing; your code still decides what happens after a gap or a reconnect. Both examples run on any plan, including Free.

```typescript TypeScript theme={"theme":"github-dark"}
import { OxArchiveWs } from "@0xarchive/sdk";

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

  const ws = new OxArchiveWs({ apiKey });
  ws.onOrderbook((coin, data) => {
    console.log(coin, data.midPrice);
  });

  await ws.connect();
  ws.subscribeOrderbook("BTC");
}

void main();
```

```python Python theme={"theme":"github-dark"}
import asyncio
import time
from os import environ
from oxarchive import OxArchiveWs, WsOptions

async def main():
    ws = OxArchiveWs(WsOptions(api_key=environ["OXARCHIVE_API_KEY"]))
    done = asyncio.Event()

    ws.on_historical_data(lambda symbol, timestamp, data: print(symbol, timestamp))
    ws.on_gap(lambda channel, symbol, start, end, minutes: print("gap", channel, symbol, minutes))
    ws.on_replay_complete(lambda channel, symbol, sent: done.set())

    await ws.connect()
    end = int(time.time() * 1000) - 60 * 60 * 1000  # one hour ago
    await ws.replay("orderbook", "BTC", start=end - 5 * 60 * 1000, end=end, speed=10)
    await done.wait()
    await ws.disconnect()

asyncio.run(main())
```

The raw protocol under these clients is described in [WebSocket connection](/websocket/connection).

## Track the connection

Pass handlers to `connect()` to record stream state, rather than treating a resolved `connect()` as the whole workflow. `onMessage` receives every server message, including the ones without a typed callback.

```typescript theme={"theme":"github-dark"}
await ws.connect({
  onOpen: () => console.log("connected"),
  onClose: (code, reason) => console.log("closed", code, reason),
  onError: (error) => console.error("websocket error", error),
  onMessage: (message) => {
    if (message.type === "error") console.error(message.error_code, message.message);
  }
});
```

Track active subscriptions, the latest message time, the reconnect count, and unsafe symbols in your application. The SDK's reconnect restores the transport; your code decides whether a local book, alert, or replay output is still safe after a close or gap, and whether to pause, resync, rebuild a book, retry a window, or mark output incomplete.

## Replay and gap callbacks

Replay data arrives through replay callbacks, not the live subscription callbacks. The typed `onHistoricalData` callback receives `coin`, `timestamp`, and `data`, but not `channel`; read `channel` from `onMessage` when a multi-channel run or a run manifest needs it.

```typescript theme={"theme":"github-dark"}
ws.onHistoricalData((coin, timestamp, data) => {
  console.log(new Date(timestamp).toISOString(), coin, data);
});

ws.onGap((channel, coin, gapStart, gapEnd, durationMinutes) => {
  console.warn("gap", channel, coin, gapStart, gapEnd, durationMinutes);
});

const end = Date.now() - 60 * 60_000; // one hour ago
ws.replay("orderbook", "BTC", {
  start: end - 5 * 60_000,             // five minutes earlier
  end,
  speed: 10
});
```

For standard timed replay, store channel, symbol, start, end, and `speed` with the output. Treat the value reported by `replay_started` as the effective speed; speed does not apply to core L4. For core L4, store `delivery: checkpoint_anchored_bulk` and checkpoint metadata instead. Also keep the `l4_snapshot` anchor and the count of ordered `l4_batch` events. Keep gap callbacks and the output destination for either delivery model. [WebSocket replay](/websocket/replay) covers the events and controls.

For multi-channel replay, send a non-empty `channels` list with one symbol. The server refuses duplicate channels, mixed venue families, and L4 channels in a multi-channel request; core L4 replay is single-channel. Keep `channel` and `channels` mutually exclusive in your code, even though the server gives `channels` precedence when both are sent.

## What to record

| Event | What to keep |
| - | - |
| Connection | Open time, close code, close reason, retry count, and active subscriptions |
| Live data | Channel, symbol, venue family, timestamp, and the latest applied state |
| Replay start | Channel or channels, symbol, start, end, delivery model, standard replay speed when it applies, and output destination |
| Standard replay snapshot | The `replay_snapshot` baseline, before any `historical_data` |
| Core L4 baseline | The `l4_snapshot`, before one or more `l4_batch` events |
| Replay data | Timestamp, channel, symbol, event type, and the order received |
| Gaps | The `gap_detected` interval and duration, the affected output, and your rebuild decision |
| End state | `replay_completed`, `replay_stopped`, an exhausted retry budget, or a stopped consumer |

The TypeScript client delivers `l4_snapshot` and `l4_batch` through `onMessage` rather than a dedicated typed callback. Python has `on_l4_snapshot` and `on_l4_batch`, and Rust delivers decoded server messages through `OxArchiveWs.rx`. In every runtime, apply a core L4 snapshot before its ordered batches and rebuild after an unsafe sequence or reconnect, as in [Order-book reconstruction](/sdks/reconstruction).

Test with one channel and one symbol before widening, and exercise close, reconnect, unsubscribe, replay snapshot, gap, and end-of-replay paths. In your application, replace each `console.log` above with the real action: update UI state, append to replay output, apply a book diff, mark a gap, or start a resync.

## Subscription helpers

| Workflow | Helper |
| - | - |
| Hyperliquid core order book | `ws.subscribeOrderbook("BTC")` |
| Live trades | `ws.subscribeTrades("ETH")` |
| HIP-4 live outcome trades | `ws.subscribeHip4("hip4_trades", "#0")` |
| Spot pair | `ws.subscribeSpot("orderbook", "HYPE-USDC")` |
| Replay | `ws.replay("orderbook", "BTC", { start, end, speed })` |

For a HIP-4 market, take an active outcome side from `GET /v1/hyperliquid/hip4/outcomes`. If a helper does not cover a channel, call `ws.subscribe(channel, symbol)` or use the raw [WebSocket](/websocket/channels) protocol.

Some SDK releases still include `stream` or `multiStream` methods. The server refuses `stream` and `stream.stop` with an `error`, because bulk WebSocket streaming is discontinued. For large file delivery, use [Data Catalog Parquet exports](/data-catalog).

## Next step

Keep a local order book from snapshots and diffs with [Order-book reconstruction](/sdks/reconstruction).


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