> For the complete documentation index, see [llms.txt](https://dev.strixlab.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dev.strixlab.io/typescript-sdk/websockets.md).

# WebSocket Streams

One socket, three channels — the live order book, the public trade feed, and your own fills.

Everything real-time shares **one connection** to `/ws`, opened lazily the first time you touch `client.ws`. Reconnects, re-subscriptions and re-authentication are handled for you.

<table><thead><tr><th width="230">Channel</th><th width="120">Auth</th><th>Gives you</th></tr></thead><tbody><tr><td><code>client.ws.orderbook</code></td><td>Public</td><td>Snapshots, deltas, last trade price, market status, mid prices.</td></tr><tr><td><code>client.ws.trades</code></td><td>Public</td><td>Every executed trade on the tokens you subscribe to.</td></tr><tr><td><code>client.ws.user</code></td><td>HMAC</td><td>Your order updates, settled fills, position deltas, portfolio summary.</td></tr></tbody></table>

## The live order book

```ts
const unsubscribe = client.ws.orderbook.subscribe([yesTokenId, noTokenId], {
  depth: 20,

  onSnapshot: ({ marketId, levels, ts }) => {
    console.log('full book', levels.bids, levels.asks);
  },

  onDelta: ({ marketId, side, price, size }) => {
    // side: 'bid' | 'ask' · size === 0 means the level is gone
  },

  onMid: ({ yesMid, noMid, bestBid, bestAsk }) => {
    console.log(`YES ${yesMid} · NO ${noMid}`);
  },

  onLastPrice:    ({ price, size }) => console.log('last trade', price, size),
  onStatusUpdate: ({ status })      => console.log('market status', status),
});

// later
unsubscribe();
```

The server sends one **snapshot** on subscribe and **deltas** thereafter. The SDK applies deltas to its cached book for you, so `onSnapshot` always hands you a complete, current book — you don't have to maintain one yourself.

{% hint style="info" %}
**Delta timing.** The venue coalesces book changes and flushes level deltas roughly every 150 ms, hold-aware — you get the net change per price level, not one message per order. Expect batched movement rather than a message per event.
{% endhint %}

### Reading the cache synchronously

Inside a quoting loop you usually want the current book *now*, with no request and no callback:

```ts
const book = client.ws.orderbook.getBook(tokenId);   // null until the first snapshot
const { yesMid, noMid, bestBid, bestAsk } = client.ws.orderbook.getMid(tokenId);
```

Unlike `orderbook.midpoint()` over REST, `getMid()` returns `null` fields on a one-sided book rather than throwing.

### Subscription mechanics

* **Reference counted.** Subscribing twice to the same token opens one server subscription; the last `unsubscribe()` closes it.
* **Depth is per token, deepest wins.** One socket serves every subscriber, so a `depth: 200` subscriber and a `depth: 20` subscriber share the deeper stream. Depth is replayed verbatim on reconnect.
* **New subscribers get the cached snapshot immediately**, before the server responds.
* No practical limit on how many tokens you subscribe to on one connection.

## The public trade feed

```ts
const unsubscribe = client.ws.trades.subscribe([tokenId], (trade) => {
  console.log(`${trade.side} ${trade.size} @ ${trade.price}`);
});
```

`side` is the **taker's** side — a `BUY` consumed resting asks. This is the feed to watch if you want to know which way flow is going, rather than just where the book sits.

## Your orders and fills

Requires credentials. Connect once, then register handlers:

{% code title="user-channel.ts" %}

```ts
client.ws.user.connect();

client.ws.user.on('order.update', (o) => {
  // status: OPEN | MATCHED | PARTIALLY_FILLED | FILLED | CANCELLED | EXPIRED
  console.log(`order ${o.id} → ${o.status}  filled ${o.filledQuantity}, ${o.remaining} left`);
});

client.ws.user.on('trade.settled', (t) => {
  console.log(`✅ ${t.side} ${t.quantity} ${t.outcomeName} @ ${t.price} · tx ${t.txHash}`);
});

client.ws.user.on('trade.failed', (f) => {
  console.error(`❌ settlement failed: ${f.reason} (${f.side} ${f.quantity})`);
});

client.ws.user.on('position.update', (p) => {
  console.log(`position ${p.sharesDelta > 0 ? '+' : ''}${p.sharesDelta} @ ${p.averagePrice}`);
});

client.ws.user.on('portfolio.summary_update', (s) => {
  console.log(`equity ${s.totalValue} · free ${s.availableBalance} · pnl ${s.totalPnl}`);
});

client.ws.user.on('error', (e) => {
  console.error('server error frame:', e.message);   // e.raw has the whole message
});
```

{% endcode %}

`on()` returns an unsubscribe function; there's also `client.ws.user.off(event, handler)`. Read the live state with `client.ws.user.connectionState` (`'disconnected' | 'connecting' | 'connected' | 'authenticated'`).

### The lifecycle you'll observe

{% stepper %}
{% step %}

#### `order.update` → `OPEN`

Your order is on the book. This fires even for an order that will cross.
{% endstep %}

{% step %}

#### `order.update` → `MATCHED`

The engine matched it. **This status exists only on the socket** — you will never see it over REST, where the matched quantity shows up as `holdQuantity` instead. Nothing is final yet.
{% endstep %}

{% step %}

#### `trade.settled` + `order.update` → `FILLED` / `PARTIALLY_FILLED`

The on-chain settlement confirmed, \~15–20 s after the match. `trade.settled` carries the transaction hash. A `position.update` arrives alongside it as an optimistic delta, ahead of on-chain indexing.
{% endstep %}

{% step %}

#### `trade.failed` (rare)

The settlement transaction did not confirm. The order returns to the book or is cancelled; your balance is untouched. Log these — a run of them means something is wrong upstream.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
**Register an `error` handler.** Failed HMAC authentication arrives as an error frame on the socket, not as a thrown exception. Without a handler the SDK falls back to `console.error`, which is easy to miss in a long-running process — and every user event silently never arrives.
{% endhint %}

## Reconnects

The connection reconnects automatically with backoff. On reconnect the SDK re-sends every live order-book and trades subscription at its tracked depth, and re-authenticates the user channel.

{% hint style="info" %}
**Deltas can be missed across a gap.** The server sends a fresh snapshot on resubscribe, so the SDK's cache re-bases itself. If you keep your own derived state (a ladder, an inventory model), rebuild it from `onSnapshot` rather than assuming continuity.
{% endhint %}

## Shutting down

```ts
process.on('SIGINT', async () => {
  await client.orders.cancelAll();
  client.destroy();          // closes the socket, clears reconnect timers
  process.exit(0);
});
```

Without `destroy()` the open socket and its timers keep the Node process alive.
