RFQ WebSocket API
The WebSocket API streams live updates: RFQ and quote status, balance snapshots, and top-of-book prices. It is read-only: every client-initiated mutation goes over the REST API; the socket carries the handshake, the subscriptions, and the pushes.
Frame-level documentation lives in the interactive WebSocket reference, rendered from the AsyncAPI 3.0 spec at /api/rfq-asyncapi.yaml.
Connecting
The shared client stream is wss://rfq-api.silhouette.exchange/v1/rfq/ws. Frames are JSON, tagged with a kind discriminator. After connecting, the client authenticates with an in-band auth frame (the credential pair from SIWE login signs a canonical GET /v1/rfq/ws, the same recipe as a signed REST request) and the server replies with a health frame. The socket is authenticated once; later frames carry no signature.
Liveness is an application-level keepalive: send {"kind": "ping"} roughly every 15 seconds and the server answers {"kind": "pong"}. A reconnect needs a fresh auth frame and fresh subscriptions.
Topics
Dispatch is opt-in: after the health frame the server sends nothing until the client subscribes. A subscribe frame names a topic and is acknowledged with subscribed; unsubscribe opts back out.
{ "kind": "subscribe", "topic": "prices", "instrumentId": "XTSLA-USDC-SPOT" }
| Topic | Scope | Streams |
|---|---|---|
prices | Public, per instrument | A price frame per top-of-book change, with the current top of book on subscribe |
balances | The authenticated user | A balanceSnapshot on every committed change, with a full snapshot on subscribe |
rfqStatus | The authenticated user's RFQs | An rfqStatus frame per status transition, with the user's open RFQs on subscribe |
quotes | The authenticated user's RFQs | A quoteUpdate frame per competing quote received (live-only) |
openRfqs | Eligible makers, per instrument | An rfq frame per open RFQ on that instrument (live-only) |
quoteStatus | Makers only | A quoteStatus frame per transition of the maker's own quotes, with recent quotes replayed on subscribe |
Topics with a baseline reply with current state on subscribe; quotes and openRfqs are live-only, so a client joining mid-auction recovers what it missed over REST. Balance snapshots are full state delivered in per-user commit order, so the last snapshot a subscriber sees is always its true current balance.
The two maker topics are refused with UNSUPPORTED_CHANNEL when the account does not qualify, and the socket stays open. quoteStatus needs an active maker record; openRfqs additionally needs approval for the instrument's pair and at least one operated settlement adapter, re-checked as each RFQ is pushed rather than only at subscribe.
Errors arrive as {"kind":"error","code":…,"message":…}. AUTH_REQUIRED closes the session, and INVALID_MESSAGE closes it too unless it answers a frame sent out of sequence. UNSUPPORTED_CHANNEL, RATE_LIMITED, INVALID_INSTRUMENT_ID, UNKNOWN_INSTRUMENT and UNAVAILABLE refuse the offending frame and leave the socket open; UNAVAILABLE changed nothing, so retry the frame.
Maker price ingest
Makers publish indicative price ladders to a separate, maker-only socket at wss://rfq-api.silhouette.exchange/v1/rfq/prices/ws. The HTTP upgrade request is authenticated with the standard HMAC headers over a canonical GET /v1/rfq/prices/ws, and each message is a bare ladder object with no kind envelope. Taker price consumption stays on the prices topic of the shared stream.
Errors on this socket use the same {"kind":"error","code":…} envelope as the shared stream, so one reader dispatching on kind handles both.