Skip to main content
This page covers subscribing to the lifecycle of your CLOB orders with subscribe_order_events, and the orderEvent payload shapes that subscription delivers.

Subscribing to order events

Subscribe to the full lifecycle of your CLOB orders by emitting subscribe_order_events. Requires an HMAC-signed handshake. See Authentication. The subscription takes no payload. It is a per-user channel scoped to the profile that signed the handshake: it delivers events for orders owned by that profile, whether you were the maker or the taker.
Sub-account orders are not included. An order you place for a sub-account is owned by that sub-account, so its events go to the sub-account’s channel, not yours. That applies however you delegated it: the onBehalfOf field on Create Order, the onBehalfOf query parameter on cancel, or the x-on-behalf-of header on read endpoints.Delegation is REST-only. There is no WebSocket equivalent, and the handshake cannot be widened to cover sub-accounts.To follow a sub-account’s order state, use a REST read that accepts delegation with the x-on-behalf-of header: POST /orders/status/batch for specific order ids, or GET /markets/{slug}/user-orders for a market’s orders. Both require delegated_signing and a sub-account the partner owns. Or authenticate as the sub-account itself.
Several event shapes arrive on the same Socket.IO event name orderEvent. Distinguish first by source, then by type:
  • OME — off-chain matching engine updates: resting-order state changes (PLACEMENT, UPDATE, CANCELLATION) and the terminal result of an immediate-or-cancel order (EXECUTION). Each change is its own frame; this stream is never coalesced, unlike orderbookUpdate. Persisting these frames gives you a complete record of everything your orders did, including cancellations, which do not appear in trade history.
  • SETTLEMENT — settlement lifecycle for CLOB trades: a provisional MATCHED the moment the engine fills your order (before the on-chain transaction), then a terminal MINED or FAILED.
One subscription per connection. Sending subscribe_order_events a second time re-binds; the previous subscription is cancelled. There is no payload. A repeat subscribe_order_events is accepted only within 30 seconds of the handshake timestamp; after that, reconnect with freshly signed headers (see Overview).
Channel is per-user, not per-market. You receive events for every order you are party to (as taker or maker). The subscription cannot be narrowed by market. Filter client-side on marketId / marketSlug if needed.
Ordering is not guaranteed across sources. OME and SETTLEMENT events for the same order can arrive in either order within a few seconds. Settlement can land before the corresponding UPDATE, or vice versa. Treat them as independent streams that both reference orderId / takerOrderId.
Resubscribe on reconnect. Subscriptions are not persisted server-side across disconnects. Your connect handler must re-emit subscribe_order_events.
Server-side deduplication. Repeated emissions within a 60-second sliding window are dropped, so retries and replays will not double-deliver. Client-side dedup is only required if you persist events across reconnects yourself.
Auth failures surface on the exception channel. If the HMAC auth headers are missing, invalid, or revoked, the guard emits a WsException to the client’s exception event and no orderEvent frames arrive. Subscribe to socket.on('exception', ...) if you want to detect auth failures rather than silently miss the stream.
Taker delay: order submission can be asynchronous. Some markets apply a short hold to marketable (taker) orders before the matching engine fills them. On such a market, POST /orders returns right away with execution.settlementStatus: "DELAYED" and an eligibleAt timestamp instead of blocking until settlement. This stream is how you observe the fill. Wait for the provisional MATCHED frame, then the terminal MINED / FAILED, correlating by orderId (the order.id from the POST /orders response) and tradeEventId; MATCHED does not carry clientOrderId. A released order produces the same frames as one that was never held: a FAK that fills partially gets one MATCHED and one terminal frame for the whole fill, plus an OME EXECUTION frame with status: "PARTIALLY_FILLED". A FAK that matches nothing when it is released emits a single OME EXECUTION frame with status: "KILLED" and no settlement frames, so treat that frame as terminal too; POST /orders/status/batch reports the same outcome as settlementStatus: "UNMATCHED". Maintenance mode can postpone delayed fills beyond eligibleAt; keep the order open in your integration until a terminal event arrives. postOnly (maker) orders are never delayed. A market’s current delay is readable as settings.takerDelayMs (milliseconds; 0 = none) on the market response.

Event payload

orderEvent

Emitted for both OME state changes and on-chain settlement results. Distinguish the two shapes by the source field.

Timestamp fields

Every orderEvent frame carries a timestamp plus additive semantic timestamps. Read occurredAt, matchedAt, and publishedAt directly. Do not substitute one for another. All timestamps are ISO-8601 UTC strings, and any missing semantic timestamp arrives as null rather than a value copied from another field.
Prefer semantic timestamps. Use occurredAt for lifecycle time, matchedAt for the persisted match time on settlement frames, and publishedAt for gateway queue time. Treat timestamp as a legacy field only.

OME event (source: "OME")

The OME source carries two shapes: ongoing lifecycle state changes (PLACEMENT / UPDATE / CANCELLATION) and a one-shot terminal result for immediate-or-cancel orders (EXECUTION). Tell them apart by type.
Lifecycle (type: "PLACEMENT" | "UPDATE" | "CANCELLATION")
Emitted for every resting-order state change recorded by the matching engine. price and remainingSize are sent unquoted as JSON numbers. Coerce defensively to preserve decimal precision.
type transitions:
  • PLACEMENT — order accepted by the OME.
  • UPDATE — remaining size changed (partial fill or amend).
  • CANCELLATION — removed from the book. Carries reason: "STP_MAKER_CANCELLED" when self-trade prevention cancelled your resting maker order against your own incoming order.
FAK/FOK terminal (type: "EXECUTION")
Emitted once when an immediate-or-cancel order reaches a recorded terminal state — a FAK (fill-and-kill), which always terminates, or a FOK (fill-or-kill) that filled. Delivered only to the order owner. Unlike the lifecycle frames, eventId is a string (terminal:<orderId>), the frame carries a status label, and price and remainingSize are strings, with remainingSize in raw 6-decimal units.
A zero-fill FAK, as observed on production:
status outcomes:
  • FILLED — the order matched in full (a FAK that matched completely, or a FOK).
  • PARTIALLY_FILLED — a FAK matched part of its size; the unfilled remainder was cancelled. remainingSize is that cancelled remainder.
  • KILLED — a FAK matched nothing and was cancelled in full. remainingSize equals the original size. A taker rejected by self-trade prevention (stpPolicy: "cancel_taker" or "cancel_both") also surfaces here as status: "KILLED". This frame carries no STP reason. The STP_TAKER_REJECTED reason is returned only on the synchronous POST /orders response.
price and remainingSize are strings on the terminal frame, unlike the lifecycle frames, where they are JSON numbers. remainingSize is in raw 6-decimal units. Parse both explicitly instead of reusing the lifecycle parser.
No fee or clientOrderId on the terminal frame. It reports only the lifecycle outcome. For the realized fee, read the POST /orders response or the MINED settlement frame. token is the raw CTF token id, not the YES / NO outcome.
A FOK is all-or-nothing. A fill-or-kill order fills completely (status: "FILLED") or is rejected. A rejected FOK returns HTTP 400 and may additionally surface as status: "KILLED" with remainingSize equal to the original size; it never emits PARTIALLY_FILLED. Only a FAK reports a partial fill (PARTIALLY_FILLED).

Settlement event (source: "SETTLEMENT")

The SETTLEMENT source carries the settlement lifecycle of a CLOB trade. Each participant receives its own events, keyed by userId: one for the taker order and one for each matched maker order. Two types arrive in sequence:
  • MATCHED — provisional. Emitted the instant the matching engine fills your order, before the on-chain settlement transaction. This is the early “your order will be matched for N” signal. Fee fields are estimates (isEstimate: true), there is no txHash yet, and the fill can still be rolled back by a later FAILED.
  • MINED / FAILED — terminal. Emitted after the settlement transaction resolves on-chain. MINED carries txHash and the taker’s realized fee; FAILED means the trade did not execute and no funds moved.
A MATCHED and its terminal MINED / FAILED share the same tradeEventId and orderId but use different eventId namespaces (matched:… vs settlement:…), so they never dedup-collide. Correlate provisional → terminal by tradeEventId + orderId (MATCHED does not carry clientOrderId). takerAccount and makerMatches[].account are on-chain addresses. For smart-wallet users this is the smart-wallet proxy; for EOA users it is the EOA. The part of an order that matches on arrival is always a single trade, however many makers it fills against. This holds for full and partial fills, for orders released after a taker delay, and for EOA and smart-wallet accounts alike. The taker gets one tradeEventId, one MATCHED and one terminal MINED / FAILED. The taker MATCHED aggregates size, cost and average price across every maker, and the terminal frame lists each maker in makerMatches. A GTC remainder that rests and fills later appears in other takers’ trades, with its own frames for each fill.
Provisional match (type: "MATCHED")
Emitted pre-chain, the moment the engine fills the order. Per-profile: the taker and each maker receive their own frame carrying their own side, token, size, price, and fee estimate (in a cross-outcome match the taker and maker hold opposite tokens). The taker frame aggregates the whole fill; each maker frame describes only that maker’s leg.
MATCHED is provisional. Do not settle books on it. Use it as an early acknowledgement / UI signal only. Its fee fields are estimates (isEstimate: true), not realized charges, and the fill can still end in FAILED. Reconcile on the terminal MINED frame, which carries txHash and the taker’s realized fee.
Fee currency follows side. A BUY fill reports the fee estimate in feeAmountContracts (feeAmountCollateral absent); a SELL fill reports it in feeAmountCollateral. The same convention applies on MINED.
Makers are not charged a fee. Only the taker pays. On a maker’s frame, the fee estimate fields (configuredFeeRateBps, effectiveFeeBps, feeAmountContracts / feeAmountCollateral) mirror the maker order’s configured rate and can be non-zero. A maker is not charged a fee on a matched CLOB trade; the realized maker fee is 0. Treat the taker-side estimate as a real pre-settlement charge to reconcile on MINED, and treat the maker-side estimate as informational only.
Terminal settlement (type: "MINED" | "FAILED")
Emitted when a CLOB trade settles on-chain. Each participant receives a settlement event for their own order: one for the taker order and one for each matched maker order.
Reconciling WS events with POST /orders. Submit an order with a clientOrderId and every orderEvent for that order — PLACEMENT, UPDATE, CANCELLATION, and the MINED / FAILED settlement frame — echoes the same clientOrderId. Match WS events to the originating request by clientOrderId rather than waiting for the POST /orders HTTP response, which only returns once settlement is MINED. If only one side supplied a clientOrderId, only that side’s event includes it.
Maker fee on MINED is informational too. Like the MATCHED estimate, a maker’s MINED fee fields are computed from the maker order’s configured rate, not a realized charge. Makers are not charged, so the realized maker fee is 0. Only the taker’s MINED fee is a realized charge.
  • type: "MINED" — on-chain settlement confirmed.
  • type: "FAILED" — settlement failed on-chain; the taker order did not execute and funds were not moved.
  • WebSocket overview: connection details, handshake authentication, and the full event reference