> 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/http-and-websocket-api/websocket.md).

# WebSocket Protocol

**Endpoint:** `wss://api.strixlab.io/ws` · testnet `wss://api-staging.strixlab.io/ws`

One connection carries every channel. There's no practical limit on how many tokens you subscribe to.

{% hint style="info" %}
On the [SDK](/typescript-sdk/websockets.md) this is all handled — subscriptions, reconnects, delta merging and re-authentication. This page is for integrating from another language.
{% endhint %}

### Connecting

On open, the server sends:

```json
{ "type": "connected" }
```

**Keep-alive.** Every 30 seconds the server sends both a native WebSocket ping and an application-level `{"type":"ping"}` frame. A connection that misses two native pong replies is terminated as stale — most WebSocket libraries answer native pings automatically, so this usually needs no code. You can also probe the connection yourself at any time:

```json
→ { "type": "ping" }
← { "type": "pong" }
```

{% hint style="warning" %}
**Slow consumers get dropped.** If your client stops reading fast enough for its subscriptions, the server terminates the connection rather than buffering indefinitely. Reconnect and re-subscribe — you'll receive a fresh snapshot.
{% endhint %}

***

## Order book channel · Public

**Subscribe** by tokenId array:

```json
{ "type": "subscribe", "markets": ["0x1a2b…", "0x3c4d…"], "depth": 50 }
```

**Unsubscribe:**

```json
{ "type": "unsubscribe", "markets": ["0x1a2b…"] }
```

### Messages

```jsonc
// Full snapshot — on subscribe, and after significant state changes
{ "type": "orderbook.snapshot", "marketId": "0x1a2b…",
  "levels": {
    "bids": [{ "price": 0.62, "size": 500 }],
    "asks": [{ "price": 0.63, "size": 200 }]
  },
  "ts": 1716000000000 }

// Incremental update — size 0 removes the level
{ "type": "orderbook.delta", "marketId": "0x1a2b…",
  "side": "bid", "price": 0.62, "size": 350, "ts": 1716000000000 }

// Fired on every settlement
{ "type": "last_trade_price", "marketId": "0x1a2b…",
  "price": 0.625, "size": 100, "ts": 1716000000000 }

// Market paused or resolved
{ "type": "market.status_update", "marketId": "0x1a2b…",
  "status": "PAUSE", "ts": 1716000000000 }
```

{% hint style="info" %}
**Maintaining the book:** take the snapshot as your baseline, then apply each delta at its price level (`size: 0` removes it). Deltas are **coalesced** — the venue flushes net level changes roughly every 150 ms rather than one message per order, so expect batched movement. If you ever suspect a gap, re-subscribe or pull `GET /orderbook/:tokenId` for a fresh baseline.
{% endhint %}

`marketId` in these frames carries the **tokenId** you subscribed with.

***

## Trades channel · Public

```json
{ "type": "subscribe", "channel": "trades", "markets": ["0x1a2b…"] }
```

```json
{ "type": "trade.executed", "tokenId": "0x1a2b…",
  "price": 0.625, "size": 100, "side": "BUY", "ts": 1716000000000 }
```

`side` is the **taker's** side — a `BUY` consumed resting asks.

***

## User channel · Auth

Authenticate on the socket; your account's events then stream automatically, with no further subscribe step.

{% tabs %}
{% tab title="HMAC (bots)" %}

```json
{
  "type":       "auth",
  "apiKey":     "your_api_key",
  "passphrase": "your_passphrase",
  "timestamp":  "1716000000",
  "signature":  "<base64 HMAC-SHA256(apiSecret, timestamp + 'GET' + '/ws')>"
}
```

The signed message is the fixed string `timestamp + "GET" + "/ws"` — see [Authentication](/http-and-websocket-api/authentication.md#websocket-authentication).
{% endtab %}

{% tab title="Privy Bearer" %}

```json
{ "type": "auth", "token": "<privy_identity_token>" }
```

For browser sessions only. Tokens expire; bots should use HMAC.
{% endtab %}
{% endtabs %}

Success returns an `authenticated` frame. **Failure returns an `error` frame, not a socket close** — handle it explicitly, or you will sit on a connected socket that never delivers an event.

### Messages

```jsonc
// Order state changed. status: OPEN | MATCHED | PARTIALLY_FILLED | FILLED | CANCELLED | EXPIRED
{ "type": "order.update", "data": {
    "id": "ord_abc", "orderHash": "0xdeadbeef…", "tokenId": "0x1a2b…",
    "status": "PARTIALLY_FILLED", "filledQuantity": 50, "remaining": 50,
    "price": 0.62, "side": "BUY", "marketId": "mkt_abc" } }

// On-chain settlement confirmed
{ "type": "trade.settled", "tradeId": "trd_abc", "side": "BUY",
  "outcomeName": "Yes", "tokenId": "0x1a2b…", "marketId": "mkt_abc",
  "marketTitle": "Will BTC close above $100k in 2026?",
  "price": 0.62, "quantity": 50, "txHash": "0x…" }

// Settlement failed on-chain
{ "type": "trade.failed", "reason": "…", "marketId": "mkt_abc",
  "side": "BUY", "quantity": 100 }

// Optimistic position delta — arrives ahead of on-chain indexing
{ "type": "position.update", "data": {
    "marketId": "mkt_abc", "outcomeId": "out_yes",
    "sharesDelta": 50, "averagePrice": 0.62 } }

// Account totals, rate-limited to about one per second
{ "type": "portfolio.summary_update", "data": {
    "totalValue": "1649.50", "availableBalance": "1188.50",
    "totalPnl": "121.30", "positionCount": 4 } }
```

{% hint style="info" %}
**`MATCHED` exists only here.** It marks the window between the engine matching your order and the on-chain settlement confirming (\~15–20 s). Over REST that quantity appears as `holdQuantity` instead. Treat `position.update` as provisional — indexed state reconciles it moments later.
{% endhint %}

***

## A minimal client

{% tabs %}
{% tab title="Python" %}
{% code title="stream.py" %}

```python
import asyncio, base64, hashlib, hmac, json, os, time
import websockets

SECRET = os.environ["STRIX_API_SECRET"]
TOKEN_ID = "0x1a2b…"

def ws_auth() -> dict:
    ts  = str(int(time.time()))
    sig = base64.b64encode(
        hmac.new(SECRET.encode(), (ts + "GET" + "/ws").encode(), hashlib.sha256).digest()
    ).decode()
    return {
        "type": "auth",
        "apiKey": os.environ["STRIX_API_KEY"],
        "passphrase": os.environ["STRIX_API_PASSPHRASE"],
        "timestamp": ts,
        "signature": sig,
    }

async def main():
    async for ws in websockets.connect("wss://api.strixlab.io/ws"):   # reconnects on drop
        try:
            await ws.send(json.dumps(ws_auth()))
            await ws.send(json.dumps({"type": "subscribe", "markets": [TOKEN_ID], "depth": 20}))

            async for raw in ws:
                msg = json.loads(raw)
                match msg.get("type"):
                    case "ping":            await ws.send(json.dumps({"type": "pong"}))
                    case "orderbook.snapshot": print("book", msg["levels"])
                    case "orderbook.delta":    print("delta", msg["side"], msg["price"], msg["size"])
                    case "trade.settled":      print("fill", msg["quantity"], "@", msg["price"])
                    case "error":              print("server error:", msg.get("message"))
        except websockets.ConnectionClosed:
            continue    # re-auth and re-subscribe on the next iteration
```

{% endcode %}
{% endtab %}

{% tab title="Node (ws)" %}
{% code title="stream.mjs" %}

```js
import WebSocket from 'ws';
import crypto from 'node:crypto';

const TOKEN_ID = '0x1a2b…';

function wsAuth() {
  const ts  = Math.floor(Date.now() / 1000).toString();
  const sig = crypto.createHmac('sha256', process.env.STRIX_API_SECRET)
                    .update(ts + 'GET' + '/ws').digest('base64');
  return { type: 'auth', apiKey: process.env.STRIX_API_KEY,
           passphrase: process.env.STRIX_API_PASSPHRASE, timestamp: ts, signature: sig };
}

function connect() {
  const ws = new WebSocket('wss://api.strixlab.io/ws');

  ws.on('open', () => {
    ws.send(JSON.stringify(wsAuth()));
    ws.send(JSON.stringify({ type: 'subscribe', markets: [TOKEN_ID], depth: 20 }));
  });

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    if (msg.type === 'ping')          ws.send(JSON.stringify({ type: 'pong' }));
    if (msg.type === 'orderbook.delta') console.log(msg.side, msg.price, msg.size);
    if (msg.type === 'trade.settled')   console.log('fill', msg.quantity, '@', msg.price);
    if (msg.type === 'error')           console.error('server error:', msg.message);
  });

  ws.on('close', () => setTimeout(connect, 1000));   // reconnect with backoff
}

connect();
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="danger" %}
**Re-authenticate and re-subscribe after every reconnect.** A reconnected socket starts with no subscriptions and no session, and it will happily sit there silent. If your strategy depends on the feed, pair the socket with a staleness timer that cancels your orders when updates stop arriving.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://dev.strixlab.io/http-and-websocket-api/websocket.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
