Trading
WebSocket events
The REST endpoint POST /api/v1/orders returns 202 Accepted with an empty trades array โ matches happen asynchronously after the response, and trade events arrive over WebSocket. This page documents the wss:// endpoint, the three channel families, the event types you can expect, and the fallback poll pattern for handling reconnect gaps.
Why a WebSocket
Endpoint
One WebSocket connection per authenticated session. Authenticate with a JWT in the URL token parameter. Per-IP rate limit applies on connection establishment but not on message volume once connected.
wss://staging.satoriex.io/ws?token=<jwt>Channels
Three channel families. The user channel binds to your session automatically; market and marketdata channels require explicit subscribe messages.
| Channel | When you get it | Event types |
|---|---|---|
| user | Automatic on every authenticated connection โ no subscribe required. | wallet_changed, deposit_confirmed, withdraw_* (6 types: requested, submitted, approved, rejected, confirmed, failed), kyc_approved/rejected, proposal_approved/rejected, referral_bonus, dispute_filed, mention, market_resolved, order_filled |
| market:{id} | After sending { type: "subscribe", market_id }. | trade, order, order_update, settlement, comment |
| marketdata:{id} | After sending { type: "subscribe_marketdata", market_id }. | price_update (mid + best bid/ask), orderbook snapshot deltas |
Subscribe + dispatch example
Browser / Node WebSocket client. The same shape works in any language with a WebSocket library โ subscribe by sending a JSON message, then dispatch on payload.type.
// Connect with bearer JWT
const ws = new WebSocket("wss://staging.satoriex.io/ws?token=" + jwt);
// Subscribe to per-market events (trades, orders, settlements, comments)
ws.send(JSON.stringify({ type: "subscribe", market_id: "mkt_abc123" }));
// Subscribe to live orderbook + price for one market
ws.send(JSON.stringify({ type: "subscribe_marketdata", market_id: "mkt_abc123" }));
// User-scope events (wallet, deposit, withdraw, KYC, proposals, notifications)
// arrive automatically on every authenticated connection โ no explicit subscribe.
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
switch (msg.type) {
case "trade": // fill on a market you're subscribed to
case "order": // order state transition
case "order_update": // resting order modified
case "settlement": // market resolved + payout posted
case "price_update": // mid moved (only on subscribe_marketdata)
case "wallet_changed": // your USDC balance changed
case "deposit_confirmed":
case "withdraw_submitted":
case "withdraw_approved":
case "withdraw_rejected":
case "withdraw_confirmed":
case "withdraw_failed":
case "kyc_approved":
case "kyc_rejected":
case "proposal_approved":
case "proposal_rejected":
case "market_resolved":
case "dispute_filed":
case "referral_bonus":
case "comment":
case "mention":
// ... handle each type
break;
}
};60-second fallback poll pattern
On reconnect, you may miss events fired while you were disconnected. The recommended pattern is a 60-second safety-net poll for any query that has a WebSocket invalidation path. This is the cadence the SatoriEx frontend uses.
- Use 60s fallback poll: market detail, market grid, portfolio โ anywhere a WebSocket event would normally invalidate the cached query.
- Keep faster polls: chart / candle data without a WebSocket source, admin tables, anywhere staleness budget is under 60s and no WebSocket invalidator exists.
- No poll: when WebSocket is reliably the only freshness path and a 60-second recovery delay on reconnect is acceptable โ rare; prefer the fallback.
Hydration gate
If your client loads initial state via REST after the WebSocket is already open, queue incoming events until your REST snapshot has hydrated. Otherwise a trade event for an order you haven't loaded yet will arrive before your /me/orders snapshot, and your local state will be inconsistent. The SatoriEx web frontend uses a user + _hydrated boolean gate before processing WS events.
Wire-format contract