WebSocket Trading
/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
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
op | Fields | Equivalent REST |
|---|---|---|
order.place | request_id?, params (a full Place Order body) | POST /orders |
order.cancel | request_id?, order_id, params (trading_key, cancel_nonce, trading_key_signature) | DELETE /orders/{id} |
order.modify | request_id?, order_id, params (a Modify Order body) | PUT /orders/{id} |
ping | request_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.
op | Fields | Meaning |
|---|---|---|
order.place | seq, request_id?, result | Order accepted; result mirrors the REST place response. |
order.cancel | seq, request_id?, result | Order cancelled. |
order.modify | seq, request_id?, result | Order modified. |
pong | seq, request_id? | Heartbeat reply. |
error | seq, request_id?, code, message | A 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
| Aspect | REST | WebSocket trading |
|---|---|---|
| Latency | Higher (TLS + HTTP per request) | Lower (one persistent socket) |
| Auth | Bearer header per request | Token once at connect; order signature per frame |
| Disconnect safety | None | Optional cancel-on-disconnect |
| Best for | One-off calls, cold starts | Long-running trading clients, market makers |
For live order state (fills, expiries), pair this socket with the Orders Channel and the Fills Channel.