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

# WebSocket Overview

> Connect to real time market data, account order updates, and order command acknowledgements.

The Stabyl WebSocket API streams real time exchange data and account specific trading updates. Use REST to discover markets and fetch durable baseline state, then use WebSocket subscriptions for low latency updates.

## Endpoints

| Environment | URL                                  |
| ----------- | ------------------------------------ |
| Production  | `wss://api.stabyl.com/v1/ws`         |
| Staging     | `wss://api-staging.stabyl.com/v1/ws` |

## Authentication

Market data topics can be used without account authentication. Private topics and order commands require an API key:

```text theme={null}
wss://api-staging.stabyl.com/v1/ws?token=<api_key>
```

<Warning>
  Because the API key is carried in the WebSocket URL, connect from server side
  automation only. Do not open API-key WebSocket connections from browser,
  mobile, desktop, or user controlled code, and do not log full WebSocket URLs.
</Warning>

## Pair and Topic Naming

REST endpoints use the pair identifier returned by `GET /partner/exchange/markets`, such as `USD/NGN`.

WebSocket topic names use a topic-safe pair key where `/` becomes `_`:

| REST pair ID | WebSocket pair key | Example topic       |
| ------------ | ------------------ | ------------------- |
| `USD/NGN`    | `USD_NGN`          | `orderbook.USD_NGN` |

Today, `USD/NGN` is the supported exchange pair. When more pairs are added, discover them from `GET /partner/exchange/markets` and derive the topic key by replacing `/` with `_`.

Use the REST pair ID in command payloads, such as `"pair_id": "USD/NGN"`. Use the topic key only inside topic names, such as `trades.USD_NGN`.

## Message Model

All client and server messages are JSON objects with a `type` field.

Client messages:

| Type          | Purpose                               |
| ------------- | ------------------------------------- |
| `subscribe`   | Subscribe to one or more topics       |
| `unsubscribe` | Stop receiving one or more topics     |
| `ping`        | Optional application-level heartbeat  |
| `command`     | Submit an authenticated order command |

Server messages:

| Type            | Purpose                                               |
| --------------- | ----------------------------------------------------- |
| `subscribed`    | Confirms active topics and returns optional snapshots |
| `unsubscribed`  | Confirms topics were removed                          |
| `data`          | Carries a topic update                                |
| `pong`          | Responds to application-level `ping`                  |
| `error`         | Reports a subscription, auth, rate, or format error   |
| `command_ack`   | Confirms an order command was accepted for processing |
| `command_error` | Reports an order command rejection                    |

## Basic Session

```mermaid theme={null}
sequenceDiagram
    participant App as Your Service
    participant WS as Stabyl WebSocket
    participant REST as Stabyl REST

    App->>REST: GET /partner/exchange/markets
    App->>REST: GET /partner/exchange/orderbook/USD%2FNGN/snapshot
    App->>WS: Connect wss://.../v1/ws?token=...
    App->>WS: subscribe orderbook.USD_NGN, trades.USD_NGN, user.orders
    WS-->>App: subscribed + optional orderbook snapshot
    WS-->>App: data messages with seq + epoch
    App->>WS: command order_create
    WS-->>App: command_ack
    WS-->>App: user.orders status update
```

## Connect From A Server

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

const baseUrl =
  process.env.STABYL_WS_URL ?? "wss://api-staging.stabyl.com/v1/ws";
const apiKey = process.env.STABYL_API_KEY;
const ws = new WebSocket(`${baseUrl}?token=${encodeURIComponent(apiKey)}`);

ws.on("open", () => {
  ws.send(
    JSON.stringify({
      type: "subscribe",
      topics: ["orderbook.USD_NGN", "trades.USD_NGN", "user.orders"],
      snapshot: true,
    }),
  );
});

ws.on("message", (raw) => {
  const message = JSON.parse(raw.toString());
  if (message.type === "data") {
    console.log(message.topic, message.seq, message.data);
  }
});
```

## Reliability Model

Every `data` message includes:

| Field   | Meaning                                                    |
| ------- | ---------------------------------------------------------- |
| `topic` | Topic that produced the update                             |
| `seq`   | Monotonic sequence for that topic within the current epoch |
| `epoch` | Stream epoch; changes after some server restarts           |
| `ts`    | Server event timestamp                                     |
| `data`  | Topic-specific payload                                     |

Store the latest `{epoch, seq}` per exact topic. On reconnect, send those values in `resume`. Resume is best-effort; if the server cannot replay from your sequence, rebuild state from REST and continue with fresh WebSocket updates.

Use exact topics when you need reliable resume. Wildcard topics are useful for broad monitoring, but exact topics give cleaner recovery behavior.

## Operational Limits

Keep each connection focused on the streams it needs:

| Limit          | Guidance                                                                       |
| -------------- | ------------------------------------------------------------------------------ |
| Subscriptions  | Up to 50 active topics per connection                                          |
| Message size   | Keep command and subscription frames compact JSON objects                      |
| Reconnects     | Use exponential backoff with jitter after disconnects or `rate_limited` errors |
| State rebuilds | Prefer REST reads for durable state, then use WebSocket for live changes       |

If a connection receives `rate_limited`, slow down, reduce the subscription set if needed, and reconnect only after a backoff delay.
