WebSocket
Real-time stream of trade updates, fills, and (optionally) order book data. Lower latency than polling REST and zero per-request rate cost beyond the 10 msg/s receive cap.
Connecting
wss://api.spreadr.xyz/api/v1/wsAuth option 1 — Header (recommended for bots)
X-API-Key: sprdr_...The API key must have trades:read scope. With this header set, the
connection authenticates as part of the WebSocket upgrade — no first
message required.
Auth option 2 — First message
If no auth header is provided, you have 5 seconds to send an auth message before the server closes the connection.
{
"type": "auth",
"api_key": "sprdr_..."
}Or with a Privy JWT (used by the web terminal):
{
"type": "auth",
"token": "<privy_jwt>"
}On success the server replies:
{
"type": "authenticated",
"timestamp": "2026-03-12T13:00:00Z"
}Subscribing
After auth, send subscribe messages to receive events.
All your trades
{
"type": "subscribe",
"data": { "subscription": "trades" }
}You'll receive updates for every trade you own — status transitions, fill events, etc.
One specific trade
{
"type": "subscribe",
"data": {
"subscription": "trades",
"params": { "trade_id": "00000000-0000-0000-0000-000000000000" }
}
}Order book stream
{
"type": "subscribe",
"data": {
"subscription": "orderbooks",
"params": { "exchange": "hyperliquid", "market": "HYPE" }
}
}Fills
{
"type": "subscribe",
"data": { "subscription": "fills" }
}Message types from server
| Type | Description |
|---|---|
authenticated | Auth successful (response to your auth message) |
trade | Trade status update (e.g. pending → active → completed) |
event | Trade event (fill, exchange disconnect, etc.) |
fill | Standalone fill notification |
orderbook | Order book snapshot for an orderbooks subscription |
position | Position update (for a positions subscription) |
maintenance | Maintenance mode change |
kill_switch | Kill switch activated or deactivated |
risk_alert | Risk warning for one of your trades |
exchange_grants | Your exchange-access grants changed |
error | Error message |
subscribed | Confirmation of a subscribe request |
unsubscribed | Confirmation of an unsubscribe request |
pong | Reply to an app-level {"type": "ping"} message |
Limits
- Max 5 concurrent WebSocket connections per user. Exceeding the cap on
the first-message auth path triggers close code
4008; on the header-auth path the connection is rejected before upgrade with HTTP429. - Message rate: 10 msg/s per connection sustained, burst of 20.
- Liveness: the server sends a protocol-level WebSocket ping every 30s;
a connection that doesn't pong within 60s is closed. Standard WS client
libraries answer these automatically. The JSON
{"type": "ping"}/pongexchange is a separate, optional app-level heartbeat — it does not count toward liveness.
Close codes
| Code | Meaning | Recover by |
|---|---|---|
4001 | Auth timeout (no auth msg within 5s) | Retry connection, send auth faster |
4002 | Invalid token / API key, or insufficient scope | Verify credentials and that the key has trades:read |
4008 | Connection limit exceeded | Close another connection first |
Example (Python)
import websocket
import json
def on_open(ws):
ws.send(json.dumps({"type": "auth", "api_key": "sprdr_..."}))
def on_message(ws, message):
msg = json.loads(message)
if msg["type"] == "authenticated":
ws.send(json.dumps({
"type": "subscribe",
"data": {"subscription": "trades"}
}))
elif msg["type"] == "trade":
print(f"Trade {msg['data']['trade_id']} -> {msg['data']['status']}")
elif msg["type"] == "event":
print(f"Event: {msg['data']['event_type']}")
ws = websocket.WebSocketApp(
"wss://api.spreadr.xyz/api/v1/ws",
on_open=on_open,
on_message=on_message
)
ws.run_forever()The frontend uses option 2 (first-message auth) with the Privy JWT so the token isn't logged in URL query parameters. If you're building a bot, prefer option 1 (header) with an API key — simpler and works the same in every WebSocket library.