WebSocket Overview
Senticore streams market data and account events over JSON WebSocket
connections. This page and the three that follow are written against the
running gateway (ws_private.rs, ws_protocol.rs, public_ws_subscribe.rs)
and the TypeScript SDK (SenticoreWsClient, ReliablePrivateStream). Where an
older page showed op/channels frames or symbol-based market names, that
protocol never shipped; the frames below are the ones the gateway accepts.
Stream families
| Family | Endpoint | Auth | Channels |
|---|---|---|---|
| Multiplexed public | wss://api.sentico-labs.xyz/api/v1/ws/public | none | marketSnapshot, l2Book, trades, bbo, activeAssetCtx |
| Multiplexed private | wss://api.sentico-labs.xyz/api/v1/ws/private/{account} (aliases /api/v1/ws/mm/{account}, /api/v1/ws/bsl/{account}) | spws1… session token from POST /api/v1/ws/token, sent in an auth frame; the issuing credential needs read scope (an institutional_agent HMAC credential works, an agent key without read gets permission_denied) | account, userEvents, orderUpdates, userFills, dropCopy |
| Snapshot + delta market feed | wss://api.sentico-labs.xyz/api/v1/feed/public, redundant lines /feed/public/a and /feed/public/b | none | Sequenced full-book snapshot and deltas with per-frame checksums; carries the placement status frame — see Market Feed |
| Server-Sent Events | https://api.sentico-labs.xyz/api/stream/market, …/api/stream/account/{account} | none / private token | Fallback for environments without WebSocket |
{account} is the 20-byte engine account in hex. Market identifiers on every
channel are numeric marketId values from GET /api/v1/public/markets
(items[]) or GET /api/v1/public/exchange-info, never display symbols.
The sandbox (launching — see Status) serves every family above at the
same paths on api.sentico-sandbox.xyz - for example
wss://api.sentico-sandbox.xyz/api/v1/ws/public. Swap the host; nothing else
changes.
What every connection gets
- The first frames announce the stream schema and the session policy. The
session frame (
"event": "session") carriesheartbeatMs,idleTimeoutMs,authDeadlineMs,maxSubscriptionsPerConnection,publicMaxMarketsPerConnection,reconnectPolicyandschemaId. Read those limits from the frame instead of hard-coding them. - Every data frame carries
channel,seqandprev_seq. Sequences are namespaced: per market on public channels, and per stream domain on private channels (account snapshot vs. execution). Private execution cursors are u64 decimal strings; compare them losslessly (BigInt) and never derive a cursor for one namespace from another by±1arithmetic. - Cursor resume: subscribe with
fromSeq(aliascursor, a decimal string on private channels) and the server replays what it still retains, then sendssnapshot_end. A resumedorderUpdatessubscription must also carrycursorNamespace: "execution-v1"(error1004otherwise). Outside the retention window — and routinely right after a deploy — it answersresume_required(1005) and you rebuild from REST. - A
rotateframe beforerotateByMsasks you to reconnect gracefully and includes a resubscribe journal. - Control frames (
ack,error,warning) share the envelope of data frames.
Client responsibilities
- Verify continuity per channel scope:
prev_seqmust equal the lastseqyou applied (public channels are contiguous; execution channels link to the durable predecessor and can jump). On a gap, stop applying and resync. - Persist the last processed private
seqper channel and resume from it. - A client
Close(1000)is answered by a server close with1000; complete the handshake instead of dropping the socket. - Optionally send
{"method": "ping"}; the server answers with anackon channelpong. Server heartbeats need no reply. - Reconnect with exponential backoff and jitter; the session frame's
reconnectPolicycarries the server's base and maximum backoff. - Keep public market recovery separate from private account recovery.