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

# Fills

> Stream live fill (execution) events for one subaccount over a single WebSocket connection.

## Endpoint

```
wss://api.rivermarkets.com/v1/ws/fills?subaccount_id=<uuid>
```

Each connection is scoped to exactly **one subaccount**. To watch fills across multiple subaccounts, open one connection per subaccount. The server pushes one frame per execution as fills arrive.

Unlike the [orders](/ws-api-reference/orders) stream — which carries only *cumulative* per-order totals (`traded_qty`, `average_price`, `fees_paid`) — this stream delivers each discrete execution with its true `exchange_trade_id`, `price`, `qty`, `fee`, maker/taker flag, and counterparty.

### Query parameters

| Parameter               | Required           | Default | Description                                                                                   |
| ----------------------- | ------------------ | ------- | --------------------------------------------------------------------------------------------- |
| `subaccount_id`         | yes                | —       | UUID of the subaccount whose fills you want to stream. Must belong to the authenticated user. |
| `key_id` / `ts` / `sig` | yes (programmatic) | —       | Ed25519 signed-handshake params. See [Authentication](/api-reference/authentication).         |
| `access_token`          | yes (browser)      | —       | Supabase JWT for first-party web clients.                                                     |

## Authentication

Browsers can't set custom headers on the WebSocket handshake, so the signed-request
flow moves to query params. The signature covers
`WS\nPATH\nSORTED_QUERY (excluding key_id/ts/sig)\nTIMESTAMP`.

<Tip>
  The official SDKs sign the handshake for you. The raw recipe below is for
  integrations without an SDK.
</Tip>

```text Signed handshake theme={null}
wss://api.rivermarkets.com/v1/ws/fills?subaccount_id=<uuid>&key_id=<uuid>&ts=<unix>&sig=<b64-sig>
```

The authenticated user must own `subaccount_id` — otherwise the connection is closed with code `4401`. A missing or malformed `subaccount_id` is closed with `4400`.

## Protocol

All frames are JSON. Server frames are bytes (orjson-serialized UTF-8). Client frames are accepted but ignored — there is no subscribe/unsubscribe action; the subaccount is fixed at handshake time.

This is a **pure live-delta stream — there is no snapshot frame**. Backfill history with [`GET /v1/fills`](/api-reference/overview) and dedup any overlap against the live stream by the composite fill identity: (`exchange_trade_id`, `exchange_order_id`, `counterparty_address`, `counterparty_exchange_order_id`). `exchange_trade_id` alone is not unique — one Polymarket trade produces one fill per matched maker order.

### Server → client

```json theme={null}
{ "type": "connected" }
{ "type": "fill",    "river_id": 12345, "exchange_trade_id": "0xdef…", /* + full fill fields */ }
{ "type": "error",   "code": "...", "message": "..." }
{ "type": "reconnect" }
```

* `connected` is sent immediately after the handshake completes.
* `reconnect` is sent during graceful pod shutdown. Reconnect with a small jitter.

### Fill payload

```json theme={null}
{
  "river_id": 12345,
  "river_order_id": "8b1f1a2e-…",
  "client_order_id": null,
  "exchange_order_id": "0xabc…",
  "exchange_trade_id": "0xdef…",
  "exchange_timestamp": "2026-04-29T13:14:18.456Z",
  "price": 0.42,
  "qty": 4,
  "fee": 0.02,
  "buy_flag": true,
  "is_maker": false,
  "counterparty_address": null,
  "counterparty_exchange_order_id": null
}
```

| Field                            | Description                                                                                                               |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `river_id`                       | Instrument the fill occurred on.                                                                                          |
| `river_order_id`                 | Internal id of the order this fill belongs to. Links a fill back to the [orders](/ws-api-reference/orders) stream's `id`. |
| `client_order_id`                | Your client-supplied order id, if one was set.                                                                            |
| `exchange_order_id`              | Exchange-side order id.                                                                                                   |
| `exchange_trade_id`              | Exchange-side trade id. Not unique on its own: a multi-maker trade shares one trade id across several fills.              |
| `exchange_timestamp`             | Execution time reported by the exchange (UTC).                                                                            |
| `price`                          | Execution price of this fill.                                                                                             |
| `qty`                            | Executed quantity of this fill.                                                                                           |
| `fee`                            | Fee charged for this execution, in the settlement currency.                                                               |
| `buy_flag`                       | `true` if this was a buy.                                                                                                 |
| `is_maker`                       | `true` if you provided liquidity (maker), `false` if you took it (taker).                                                 |
| `counterparty_address`           | On-chain counterparty address where applicable, else `null`.                                                              |
| `counterparty_exchange_order_id` | Exchange-side id of the counterparty's order where applicable, else `null`.                                               |

### Keepalive

Liveness is handled at the WebSocket protocol layer (RFC 6455 PING/PONG control frames). Standard clients (Python `websockets`, browser `WebSocket`) reply to server PINGs automatically — no application-level heartbeat is needed.

## Minimal client

Using the SDK (handles signing automatically):

```python theme={null}
import asyncio
from rivermarkets import AsyncRiverMarkets

async def run(subaccount_id: str):
    client = AsyncRiverMarkets(key_id="<uuid>", private_key="<base64-priv>")
    async with client.ws.fills(subaccount_id=subaccount_id) as stream:
        async for msg in stream:
            if msg.type == "fill":
                print(msg.exchange_trade_id, msg.qty, "@", msg.price, "fee", msg.fee)

asyncio.run(run("<subaccount-uuid>"))
```

Or signing the handshake manually:

```python theme={null}
import asyncio, base64, json, time
from urllib.parse import urlencode, quote
import websockets
from nacl.signing import SigningKey

KEY_ID = "<uuid>"
signing_key = SigningKey(base64.b64decode("<base64-priv>"))


def sign_ws(path: str, extra_params: dict) -> str:
    sorted_q = urlencode(sorted(extra_params.items()), quote_via=quote)
    ts = str(int(time.time()))
    canonical = "\n".join(["WS", path, sorted_q, ts]).encode()
    sig = base64.b64encode(signing_key.sign(canonical).signature).decode()
    return f"wss://api.rivermarkets.com{path}?{urlencode({**extra_params, 'key_id': KEY_ID, 'ts': ts, 'sig': sig})}"


async def run(subaccount_id: str):
    async with websockets.connect(sign_ws("/v1/ws/fills", {"subaccount_id": subaccount_id})) as ws:
        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] == "fill":
                print(msg["exchange_trade_id"], msg["qty"], "@", msg["price"], "fee", msg["fee"])

asyncio.run(run("<subaccount-uuid>"))
```

## Errors

Recoverable problems arrive as an `error` frame followed by a close. `code` is stable and safe to branch on; `message` is human-readable.

| `code`                  | When                                                      |
| ----------------------- | --------------------------------------------------------- |
| `unauthorized`          | API key / JWT was missing or invalid.                     |
| `missing_subaccount_id` | `?subaccount_id=` was not supplied on the handshake.      |
| `invalid_subaccount_id` | `subaccount_id` is not a valid UUID.                      |
| `subaccount_forbidden`  | The subaccount does not belong to the authenticated user. |

## Close codes

| Code   | Meaning                                                                      |
| ------ | ---------------------------------------------------------------------------- |
| `1000` | Normal closure                                                               |
| `1011` | Server-side overflow (client couldn't keep up with the send rate)            |
| `4400` | Bad request (missing/invalid `subaccount_id`)                                |
| `4401` | Missing or invalid authentication, or subaccount does not belong to the user |
| `4503` | Server draining for deploy — reconnect with jitter                           |
