# Responses and events

Match SBE WebSocket answers to commands, subscribe to private order and execution events, and recover after a gap with MarketResync or a reconnect.

The server sends two kinds of messages: answers, exactly one per command you send, and private events, which you receive only after subscribing. Both arrive on the same connection.

## Answers

- **`Ping`** · success `Pong` (22) · failure `CommandReject` (21)
- **`Subscribe`** · success `SubscriptionAck` (23) · failure `CommandReject` (21)
- **`CreateOrder`, `CancelOrder`, `ModifyOrder`, `CancelAllOrders`** · success `CommandAck` (20) · failure `CommandReject` (21)
- **`BatchCreateOrders`** · success `BatchCreateAck` (26) · failure `CommandReject` (21)
- **`BatchReplaceOrders`** · success `BatchReplaceAck` (27) · failure `CommandReject` (21)
- **`BatchCancelOrders`** · success `BatchCancelAck` (28) · failure `CommandReject` (21)

Every answer echoes the command's `seq` and `correlationId`. A `CommandReject` for a frame that could not be decoded carries `0` in both. `CommandAck.commandType` names the command it answers.

A batch acknowledgement can contain rejected items; a `CommandReject` for a batch means no item result is available. [Order entry](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/order-entry) describes what each acknowledgement contains, and [Errors and rate limits](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/errors-and-rate-limits) what each reject code means.

## Subscribe to private events

`Subscribe` sets the complete list of event channels for the connection. Each `Subscribe` replaces the previous one, and `0` turns all events off.

| Channel and bit        | Delivers                                                                     |
| ---------------------- | ---------------------------------------------------------------------------- |
| `executions` · bit 0   | `ExecutionEvent` for each fill of your orders                                |
| `orders` · bit 1       | `OrderEvent` for each state change of your orders                            |
| `marketResync` · bit 2 | `MarketResync` instead of a `1012` close when a Pair's events may be missing |

`marketResync` takes effect only together with `executions` or `orders`.

`SubscriptionAck.channels` confirms the set now active. The change is atomic: events written before the acknowledgement follow the previous set, events after it follow the new one.

Events cover every order of the account the session is bound to, whichever interface placed it, including orders placed by triggers. They start when you subscribe: there is no snapshot or replay of earlier events. Load current state first, for example with [`GET /v1/orders/open`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/orders/open), then apply events.

## ExecutionEvent

One `ExecutionEvent` reports one fill of one of your orders. The same execution can be sent twice: first with `settlementStatus` `PENDING` as soon as it matches, then with `SETTLED` once financial settlement is final.

| Field                            | Use                                                                                                                                                               |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `symbolId` `orderId`             | The Pair and your order                                                                                                                                           |
| `matchId`                        | Trade ID, increasing within the Pair. Deduplicate on `symbolId`, `matchId`, and `orderId`                                                                         |
| `matchSeq`                       | Execution sequence within the Pair. Gaps are expected: you see only your own executions                                                                           |
| `side` `liquidityRole`           | Your side, and whether you were `MAKER` or `TAKER`                                                                                                                |
| `fillQtyScaled` `fillPriceTicks` | Quantity and price of this fill                                                                                                                                   |
| `remainingQtyScaled`             | Your order's remaining quantity right after this fill; null on `SETTLED` notifications                                                                            |
| `feeAsset`                       | The fee Asset. The fee amount is not included; read it from [trade history](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/spot/trades) after settlement |
| `matchTsNs`                      | Execution time                                                                                                                                                    |
| `gatewayTsNs`                    | When this message was encoded. Subtract `matchTsNs` from it to estimate server-side delivery latency                                                              |

Act on `PENDING` for trading decisions. Use `SETTLED`, or trade history, for accounting. A `SETTLED` execution can reach trade history shortly afterward, so read again when it is not there yet. See [Wait for history to catch up](https://testnet.polyester.com/docs/developer-docs/shared-concepts/fills-and-trade-history#wait-for-history-to-catch-up).

## OrderEvent

One `OrderEvent` reports one state change of one of your orders.

| Field                        | Use                                                                                                                           |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `symbolId` `orderId`         | The Pair and your order                                                                                                       |
| `version`                    | Starts at `1` and increases with each change. Ignore an event whose version is not newer                                      |
| `status`                     | State after this change                                                                                                       |
| `remainingQtyScaled`         | Unexecuted quantity; `0` in a terminal state                                                                                  |
| `cumulativeQtyScaled`        | Total executed quantity across the order's lineage, including fills of the orders it replaced                                 |
| `rejectReason`               | For `REJECTED`: the same code the Orders API returns for that refusal; `0` otherwise                                          |
| `cancelReason`               | For `CANCELED`: why the order ended, for example `USER_REQUEST`, `IOC_REMAINDER`, `REPLACED`, or `GTD_EXPIRED`; `0` otherwise |
| `timeInForce` `expireAtTsNs` | The order's time in force and, for `GTD`, its expiry                                                                          |
| `eventTsNs` `gatewayTsNs`    | When the change happened and when this message was encoded                                                                    |

This interface sends the statuses `PENDING`, `PENDING_CANCEL`, `WORKING`, `FILLED`, `CANCELED`, and `REJECTED`. The schema's `PENDING_REPLACE` status is never sent here. `FILLED`, `CANCELED`, and `REJECTED` are terminal. An order still executes while it is `PENDING_CANCEL`. A replaced order ends `CANCELED` with `cancelReason` `REPLACED`, and its successor reports its own events under its new `orderId`.

A gap in an order's versions means you missed a change of that order. Read the order with [`GET /v1/spot/orders/{order_id}`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/spot/orders/order_id) (ConnectRPC [`GetOrder`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersReadService/GetOrder)).

## Ordering

The server keeps three guarantees on each connection:

- Answers arrive in the order you sent the commands.
- The answer to a command arrives before any event that command caused. After you read a create's `CommandAck`, you know its `orderId` before its first `OrderEvent`.
- Events arrive in the order the server received them.

Between an answer and an event the command did not cause, there is no guaranteed order. An event of an existing order can arrive while a later command is still unanswered.

## Recovery

Events are a live feed, not the durable record. The server tells you when you may have missed some, and you recover by reading the durable state over REST or ConnectRPC.

### MarketResync or a 1012 close

When the server detects that private events of a Pair may have been missed, of any account, it acts on every connection that subscribes to events:

- With `marketResync` enabled, it sends `MarketResync` for that Pair and keeps the connection open. Events of every other Pair are unaffected.
- Without it, it closes the connection with `1012`.

`MarketResync` can arrive for a Pair you do not trade, and one interruption can send it more than once. Its `reason` is `INTERRUPTED` or `DISCONTINUITY`; the recovery is the same for both. When only your account's events may be missing, or event delivery is unavailable, the connection closes with `1012` even with `marketResync` enabled.

1. Mark the Pair as uncertain

   On `MarketResync`, keep applying events of other Pairs. Hold decisions that depend on your state in the named Pair.

2. Read the Pair's state

   Read your open orders and recent executions on that Pair over REST or ConnectRPC.

3. Apply later events

   Apply the Pair's events that arrive after the `MarketResync`, in order. They may repeat state your read already returned: skip order events whose `version` is not newer, and duplicate executions.

After a `1012` close, follow [Reconnect](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/connection-and-authentication#reconnect) and reconcile every Pair you trade.

### Market halts

Halts are exceptional: the exchange halts a market only to protect its integrity. A halt cancels every open order of the Pair with cancel reason `MARKET_HALTED` (17) and ends its triggers. Fills before the cancel stand, and the market refuses new orders until it trades again.

The halt's individual cancels are not pushed. At the halt, a session receives `MarketResync` with reason `INTERRUPTED` for the Pair, or a `1012` close without `marketResync`; reconcile the Pair as above. An order the halted market refuses after admission arrives as an `OrderEvent` with status `REJECTED` and `rejectReason` `MARKET_HALTED` (56), even though its `CreateOrder` was acknowledged.

### Slow readers

Each connection holds a bounded queue of events waiting to be written to you. It absorbs bursts, such as the order events of a large cancel-all. If you stop reading, the queue fills and the server closes the connection with `1008` (`private event queue overflow`). Read the socket on a dedicated thread or task and hand messages to your strategy through your own queue.

> **Answers also back up**
>
> A connection that does not read also stops receiving answers. The server pauses reading your commands once 1,024 are unanswered, and closes the connection when a write to you stalls for 10 seconds.
