Skip to main content

WebSocket Trading

TL;DR

/ws/trading is a bidirectional socket for order submission. Stream framed order.place / order.cancel / order.modify requests and receive one reply per frame, dispatched to the same intake and verification the REST endpoints use. The wins over REST: one warm, pre-authenticated connection (no TLS + bearer round-trip per request) and cancel-on-disconnect for market makers.

Connect

wss://<gateway-host>/ws/trading?token=<access_token>

The socket self-authenticates with the bearer token as ?token= (an Authorization: Bearer header is also accepted). To enable cancel-on-disconnect, add &cancel_on_disconnect=true.

wss://<gateway-host>/ws/trading?token=<access_token>&cancel_on_disconnect=true
The order signature is still required

Authenticating the socket establishes who is connected, not who owns an order. Every order.place / order.cancel / order.modify frame still carries the per-order trading-key signature (the same one the REST endpoints require). An authenticated socket cannot move another key's orders.

Message format

Every frame is JSON, tagged by op. Requests may carry a request_id, which the reply echoes so a client can correlate responses on the multiplexed socket.

Request frames

opFieldsEquivalent REST
order.placerequest_id?, params (a full Place Order body)POST /orders
order.cancelrequest_id?, order_id, params (trading_key, cancel_nonce, trading_key_signature)DELETE /orders/{id}
order.modifyrequest_id?, order_id, params (a Modify Order body)PUT /orders/{id}
pingrequest_id?none
{ "op": "order.place", "request_id": "r1", "params": { "symbol": "SOL-USDC", "side": "bid", "…": "…" } }

Reply frames

Every reply carries a per-connection monotonic seq (starting at 1) so a client can detect a dropped frame.

opFieldsMeaning
order.placeseq, request_id?, resultOrder accepted; result mirrors the REST place response.
order.cancelseq, request_id?, resultOrder cancelled.
order.modifyseq, request_id?, resultOrder modified.
pongseq, request_id?Heartbeat reply.
errorseq, request_id?, code, messageA frame failed. code is the stable numeric error code; message is the same reason the REST path would have returned.
{ "op": "order.place", "seq": 1, "request_id": "r1", "result": { "order_id": "aa…01", "status": "accepted", "arrival_slot": 309482113 } }
{ "op": "error", "seq": 2, "request_id": "r2", "code": 1102, "message": "trading_key_signature does not verify against the canonical body" }

Cancel-on-disconnect

When you connect with ?cancel_on_disconnect=true, the engine tracks the orders placed on this socket and, when the socket closes, cancels the ones still resting. This protects a market maker that loses connectivity from leaving stale quotes crossing.

You can also set an account-wide default so every socket gets the behavior without the query param: PUT /account/settings with { "cancel_on_disconnect_default": true }. An explicit ?cancel_on_disconnect= on a socket always overrides the account default for that connection.

The teardown is a server-initiated cancel using each order's own booked key. It needs no client signature, because the order was placed on this authenticated session and a cancel only un-rests an order (it never settles or moves funds).

Orders that have already filled, expired, or been cancelled are left as-is; only still-resting orders from this session are swept.

Heartbeat

Send a ping frame periodically to keep the connection live and detect a half-open socket; the server replies pong. Transport-level WebSocket pings are also answered.

Example

const ws = new WebSocket(`${WSS}/ws/trading?token=${TOKEN}&cancel_on_disconnect=true`);
let id = 0;
const next = () => `r-${++id}`;

ws.onopen = () => {
ws.send(JSON.stringify({ op: "order.place", request_id: next(), params: order }));
};

ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.op === "error") console.error("rejected", msg.code, msg.message);
else if (msg.op === "order.place") console.log("accepted", msg.result.order_id);
};

setInterval(() => ws.send(JSON.stringify({ op: "ping", request_id: next() })), 20000);

REST vs. WebSocket

AspectRESTWebSocket trading
LatencyHigher (TLS + HTTP per request)Lower (one persistent socket)
AuthBearer header per requestToken once at connect; order signature per frame
Disconnect safetyNoneOptional cancel-on-disconnect
Best forOne-off calls, cold startsLong-running trading clients, market makers

For live order state (fills, expiries), pair this socket with the Orders Channel and the Fills Channel.