Connection
URL:wss://ws.limitless.exchange
Namespace: /markets
Transport: WebSocket only (no polling fallback)
Authenticated channels (positions, order events) require an HMAC-signed handshake. Sign a fixed canonical message — {ISO-8601 timestamp}\nGET\n/socket.io/?EIO=4&transport=websocket\n — with the base64-decoded secret and pass three headers as extraHeaders:
TOKEN_ID and SECRET. If you use an official SDK, prefer its WebSocket client, which signs the handshake automatically from hmacCredentials (TypeScript) / hmac_credentials (Python) / WithHMACCredentials (Go).
Subscribing to Market Data
Subscribe to price and orderbook updates by emittingsubscribe_market_prices:
Subscribing to Position Updates
Subscribe to real-time position changes by emittingsubscribe_positions. This requires an HMAC-signed handshake — see Authentication.
subscribe_positions accepts the same payload as subscribe_market_prices:
Position updates are pushed automatically when your balances change (e.g. after a trade is mined). You do not need to poll.
Subscribing to Order Events
Subscribe to the full lifecycle of your CLOB orders by emittingsubscribe_order_events. Requires an HMAC-signed handshake — see Authentication. The subscription takes no payload — it is a per-user channel that delivers events for every order you are party to.
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).SETTLEMENT— settlement lifecycle for CLOB trades: a provisionalMATCHEDthe moment the engine fills your order (before the on-chain transaction), then a terminalMINEDorFAILED.
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.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.
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 — so this stream is how you observe the fill: wait for the provisional MATCHED frame, then the terminal MINED / FAILED, correlating by clientOrderId / tradeEventId. 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.Subscribing to Market Lifecycle Events
Subscribe to market creation and resolution events by emittingsubscribe_market_lifecycle. No authentication required.
marketResolved is also emitted to existing per-market room subscribers automatically (CLOB: market:{slug}, AMM: market:{address}) — you don’t need a separate lifecycle subscription to receive resolution events for markets you’re already watching.Event Reference
Event Payloads
newPriceData
orderbookUpdate
Emitted for CLOB markets when the book changes (new bids/asks, removals, or fills). orderbook carries the full updated book — each side is a sorted array of price levels.
price and size are JSON numbers; coerce defensively to preserve decimal precision. For a one-shot snapshot, use GET /markets/{slug}/orderbook — the same shape.positions
Position updates have different shapes depending on market type.
AMM markets:
marketCreated
Emitted when a new market is funded and visible. Hidden markets are excluded.
marketResolved
Emitted when a market resolves. Sent to both market_lifecycle subscribers and existing market:{slug} room subscribers.
orderEvent
Emitted for both OME state changes and on-chain settlement results. Distinguish the two shapes by the source field.
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. Carriesreason: "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>) and the frame carries a status label.
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.remainingSizeis that cancelled remainder.KILLED— a FAK matched nothing and was cancelled in full.remainingSizeequals the original size. A taker rejected by self-trade prevention (stpPolicy: "cancel_taker"or"cancel_both") also surfaces here asstatus: "KILLED"; this frame carries no STP reason — theSTP_TAKER_REJECTEDreason is returned only on the synchronousPOST /ordersresponse.
price and remainingSize are JSON numbers (as on the lifecycle frames) — coerce defensively to preserve decimal precision.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 either fills completely (
status: "FILLED") or is rejected with HTTP 400 and produces no event — it never emits PARTIALLY_FILLED or KILLED. Only a FAK reports a partial (PARTIALLY_FILLED) or zero (KILLED) fill.Settlement event (source: "SETTLEMENT")
The SETTLEMENT source carries the settlement lifecycle of a CLOB trade. Each participant receives its own events — one for the taker order and one for each matched maker order, keyed by userId. 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 notxHashyet, and the fill can still be rolled back by a laterFAILED.MINED/FAILED— terminal. Emitted after the settlement transaction resolves on-chain.MINEDcarriestxHashand the taker’s realized fee;FAILEDmeans the trade did not execute and no funds moved.
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.
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.
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, but 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; 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.