# User Data Streams Overview

Executions, order changes, position changes and balance changes, pushed as they happen.

This is the **only** low-latency way to learn that an order filled. `POST /v1/order` with the
default `ACK` tells you the order was accepted; the fill arrives here.

## Connect

1. Create a listenKey: [`POST /v1/listenKey`](/user-data-streams/start-user-data-stream.md).
2. Connect to:

```
wss://trade-stream.yellowboxmarkets.com/ws/<listenKey>
```

3. Keep the key alive with [`PUT /v1/listenKey`](/user-data-streams/keepalive-user-data-stream.md)
   every **30 minutes**.

There is no `SUBSCRIBE` step. The connection starts delivering immediately and carries **everything
in the key's scope** — exactly the logins [`GET /v1/accounts`](/account.md) lists. A login
the REST endpoints would answer `-2022` for (for example an archived account) is not streamed either.

> **One connection covers every login the key may trade.** There is no per-login stream and no
> per-login listenKey. **Every event carries `L`, the MT5 login it belongs to** — route on it.

The connection limits are the same as the market streams: server ping every 3 minutes, dropped
after 10 minutes without a pong, 24-hour maximum lifetime, 10 inbound messages per second (inbound
messages are otherwise ignored on this stream). See
[websocket-market-streams.md § Connection limits](/websocket-market-streams.md#connection-limits).

- A listenKey that is unknown, expired or deleted is not refused at the HTTP level: the connection
  opens, receives one [`listenKeyExpired`](/user-data-streams/listenkeyexpired.md) event and is closed.
- While the trading surface is switched off, the upgrade is refused with HTTP 503 and `-1017`.

## Common fields

Every event carries these three:

| Field | Type | Description |
| - | - | - |
| `e` | STRING | Event type. |
| `E` | LONG | Event time, Unix ms. |
| `L` | LONG | MT5 login the event belongs to. |

`listenKeyExpired` is the exception — it is a connection-level event and carries no `L`.

## Ordering

- **Events for one login are ordered by `E`.** `E` is non-decreasing within a login.
- **There is no ordering guarantee across logins.** One login's events may interleave arbitrarily
  with another's. Do not use `E` as a global sequence.
- `DEAL`, `ORDER_UPDATE` and `POSITION_UPDATE` describing the same fill can arrive in any order
  relative to each other, and can share an `E`. Do not build a state machine that requires
  `DEAL` before `POSITION_UPDATE`.
- **`DEAL` can repeat.** Dedupe on `d`. `ORDER_UPDATE` and `POSITION_UPDATE` can also repeat with
  identical content; treat them as **state snapshots**, applying the latest by `E`, rather than as
  deltas.

Because they are snapshots, an out-of-order or duplicate `POSITION_UPDATE` is harmless if you
compare `E` and keep the newest — which is the recommended way to hold position state.

## Connection close

Besides the ordinary cases — you closed it, the 24-hour lifetime elapsed, the server restarted —
two closes carry a reason you should act on. The reason is the WebSocket close frame's description,
not a JSON event.

| Close code | Reason | What happened | What to do |
| - | - | - | - |
| `1011` | `queue overflow; resnapshot` | Your connection was not reading fast enough and the server's outbound buffer for it filled — or the server's own intake for your logins fell behind under a burst. The alternative was to drop frames silently, and this stream has no sequence number, so you could not have detected that. | **Assume you missed events**, including fills. Reconnect, then **resnapshot** (below) before trusting your state, and read `GET /v1/userTrades` from your last `dealId`. |
| `1008` | `scope empty; check /v1/accounts` | Every account the API key could see has left its scope — the accounts were closed, their owner was disabled, or the key's configuration changed. Nothing can be delivered on this connection any more. | Do **not** reconnect in a loop. Read `GET /v1/accounts`; if it is empty, the key has no accounts and this is an operator change, not a fault. |

The scope is re-checked while a connection is open, not only when it is accepted: a login removed
from the key's scope stops delivering within about 30 seconds, matching when REST stops answering
for it. The connection stays open as long as at least one login remains.

## Reconnect

**Events that occurred while you were disconnected are not replayed.** There is no sequence number
to resume from and no backfill.

After any reconnect — a new listenKey, a dropped connection, the 24-hour lifetime, a process
restart — **resnapshot before trusting your state**:

```
GET /v1/positions?login=...      per login in scope
GET /v1/openOrders?login=...     per login in scope
```

Then apply stream events on top, keeping the newest `E` per entity.

For executions you may have missed, read
[`GET /v1/userTrades`](/account/account-trade-list.md) with `fromId` set to the last
`dealId` you processed. That is the durable record; the stream is the fast one.

Reconnect with exponential backoff and jitter.

## listenKey management

Base URL `https://trade-api.yellowboxmarkets.com`. Conventions, signing and error format:
[General Info](/general-info.md).

These three endpoints manage the **listenKey** that authenticates a user data stream connection.
The events themselves are documented in [User Data Streams Overview](/user-data-streams.md).

| Endpoint | Security | Weight |
| - | - | - |
| [`POST /v1/listenKey`](/user-data-streams/start-user-data-stream.md) | `USER_STREAM` | 1 |
| [`PUT /v1/listenKey`](/user-data-streams/keepalive-user-data-stream.md) | `USER_STREAM` | 1 |
| [`DELETE /v1/listenKey`](/user-data-streams/close-user-data-stream.md) | `USER_STREAM` | 1 |

`USER_STREAM` endpoints need the `X-YBX-APIKEY` header but **no signature** — there is nothing to
sign, and the key itself is the credential. They take no `login`: one listenKey covers **every**
login the key may trade.
