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

# WebSocket

> One connection per chain, JSON messages, snapshot then deltas, heartbeats, and private channels with the same signature as REST.

Connect to `wss://api.gains.trade/v1/{chain}/ws`. The server greets with `{"op":"hello","chain":"arbitrum","heartbeatMs":15000}`.

## Messages you send

```json theme={null}
{"op":"subscribe","channel":"prices"}
{"op":"subscribe","channel":"book","market":"BTC/USD"}
{"op":"subscribe","channel":"trades","market":"BTC/USD"}
{"op":"subscribe","channel":"candles","market":"BTC/USD","interval":"1m"}
{"op":"unsubscribe","channel":"book","market":"BTC/USD"}
{"op":"ping"}
{"op":"pong"}
```

Private channels need one `auth` message first, then the same `subscribe`:

```json theme={null}
{"op":"auth","agent":"0x...","timestamp":"1700000000000","nonce":"1700000000000001","signature":"0x..."}
{"op":"auth","apiKey":"gk_..."}
{"op":"subscribe","channel":"orders"}
{"op":"subscribe","channel":"positions"}
{"op":"subscribe","channel":"fills"}
{"op":"subscribe","channel":"account"}
```

The `auth` signature is the REST [request signature](/developer/trading-api/authentication#signing-a-request) with `primaryType: "wsAuth"`, `method: "WS"`, `path: "/v1/{chain}/ws"` and the empty-body hash. `timestamp` and `nonce` are strings here.

## Messages you receive

Every subscription is acknowledged with `{"op":"subscribed","channel":...,"market":...}` and the first data message carries `snapshot: true`. Deltas follow with `snapshot: false`.

| Channel     | Snapshot                          | Deltas                                                                                          |
| ----------- | --------------------------------- | ----------------------------------------------------------------------------------------------- |
| `prices`    | Every market's `mark` and `index` | Batches every 100 ms, `seq` increments by one per message                                       |
| `book`      | The full virtual book             | The full book again whenever it changed, at most every 500 ms, with `seq`                       |
| `trades`    | The last 50 trades                | New trades only                                                                                 |
| `candles`   | The last 200 candles              | The live candle whenever it changed, at most once per second                                    |
| `orders`    | Your open orders                  | One order per transition (`pending` to `filled`, `rejected`, `timed_out`, `open` to `canceled`) |
| `fills`     | Empty                             | One order per fill                                                                              |
| `positions` | Your open positions with live PnL | The full list whenever it changed, every 5 s                                                    |
| `account`   | Balances, fee tier, agents        | Every 10 s                                                                                      |

```json theme={null}
{"channel":"prices","snapshot":false,"seq":42,"data":[{"market":"BTC/USD","mark":"65001.5","index":"65000","updatedAt":1700000000025}]}
{"channel":"orders","snapshot":false,"data":{"id":"5316911983139663","status":"filled","fill":{"price":"65002","size":"0.001","positionId":"9007199254740991", "...":"..."}}}
{"op":"error","code":"UNAUTHENTICATED","message":"send an auth message before subscribing to private channels"}
```

Data objects have the same shape as the REST resources (`Price`, `OrderBook`, `Candle`, `RecentTrade`, `Order`, `Position`, `Account` in the reference).

## Heartbeat and reconnects

The server sends `{"op":"ping","timestamp":...}` every 15 seconds and closes the connection after 45 seconds without any message from you; answer with `{"op":"pong"}` or send your own `ping`. On `book` and `prices`, a gap in `seq` means you missed a message: unsubscribe and subscribe again to get a fresh snapshot.

After a reconnect, authenticate again and re-send your subscriptions. The SDK's `GainsWebSocket` does both automatically and exposes channels as event listeners or async iterators.

## Limits

Client messages are capped at 4 KB. One connection carries any number of channels; open one connection per chain rather than one per channel.
