Skip to main content

Realtime Socket

Live streaming over websocket: public market data for any product, and your own trading activity, pushed as it happens. The socket is a Centrifugo server, so any Centrifugo client SDK works — centrifuge-js, centrifuge-go, centrifuge-python, and so on.

Endpoint: wss://stream.tradepost.co/connection/websocket

Authenticating

The socket takes a short-lived connection token, minted by the API with your normal authentication:

query {
getRealtimeToken {
token
expiresAt
error { message }
}
}

The token is a JWT whose sub is your account id. It lives twenty minutes — wire the SDK's getToken callback to this query and it re-fetches before Centrifugo drops the socket:

import { Centrifuge } from 'centrifuge';

const centrifuge = new Centrifuge('wss://stream.tradepost.co/connection/websocket', {
getToken: async () => {
const { data } = await graphql(`query { getRealtimeToken { token } }`);
return data.getRealtimeToken.token;
},
});
centrifuge.connect();

One connection carries every subscription you need. Public channels need nothing beyond the connection; private account: channels are authorized by the #<accountId> suffix matching the token's sub — subscribing to any other account's channel is refused with 103 permission denied.

Channels

Channel names are <namespace>:<stream>:<scope>. Public streams are scoped to an asset configuration and, for most, a graded/condition key — the same (assetConfigurationId, keyId) pair that names one book. Private streams are scoped to your account.

Public market data

ChannelPayloadWhat it carries
public:book:<configId>:<keyId>Book deltaOne depth level after it changed — see Order Book
public:book:<configId>:<keyId>:<marketId>Book deltaThe same, scoped to one venue's aggregates
public:trades:<configId>:<keyId>Trade printAnonymized fill: executionId, price, qty, takerSide, marketId, executedAt
public:stats:<configId>:<keyId>Leaf statsRecomputed market stats after each trade: last price, volume, VWAP, high/low, trade count, sparkline, with a seq to drop stale pushes

Your account

ChannelkindWhat it carries
account:fills#<accountId>fillA fill on one of your orders — full trade detail, your side, your order id, status and version. Re-pushed on every settlement transition
account:fills#<accountId>orderAn order lifecycle event — orderId, status, action, reason, filledQuantity, remainingQuantity. Emitted on rest, amend, cancel, expiry, and settlement-driven changes
account:money#<accountId>balance / profileYour cached balance, and your payment profile, when either changes
account:holdings#<accountId>holdingsA poke — your inventory changed, refetch. Never a delta

Two payload kinds share account:fills, discriminated by kind. Fill payloads carry a version: keep the highest per executionId, not the last to arrive.

Snapshot, then stream

The socket is a delta feed. Every channel is meant to be paired with a read from the API that gives you the starting state:

  1. Subscribe to the channel first, so nothing lands between your read and your subscription.
  2. Read the snapshot — getOrderBook, listOrderSubmissions, listOrderExecutions.
  3. Apply pushes on top. Book deltas carry a seq to align against the snapshot's sequence; fill payloads carry a version; order events carry updatedAt.

Channels keep a short history (roughly five minutes for public streams, ten for account streams), so a brief disconnect recovers automatically through the SDK. After anything longer, or on any RESYNC marker, re-read the snapshot.

Delivery is at-most-once from the server's point of view — a rolled-back write emits nothing, and a client that is offline misses ephemeral pushes. Reconcile from the API whenever you reconnect.