WebSocket General Info — Spot
Basic connection guide, authentication, channel listing, and protocol rules for the ZTDX spot WebSocket.
Base URL
| Environment | WebSocket URL |
|---|---|
| Testnet (Sepolia) | wss://api.prex.world/ws |
| Mainnet | (not deployed yet) |
The spot WebSocket shares the same endpoint as the perp WebSocket. A single connection can subscribe to any mix of spot and perp channels simultaneously.
Connection
- All messages are JSON text frames (RFC 6455).
- Send a
pingevery 30 seconds to keep the connection alive. - A single connection supports multiple concurrent subscriptions.
- Unknown spot channels are rejected with
INVALID_CHANNEL; the connection stays open.
Connection Example
- JavaScript
- Python
const ws = new WebSocket('wss://api.prex.world/ws');
ws.onopen = () => {
console.log('Connected');
// Public channel — no auth needed
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'spot:depth:DFUSDT',
}));
// Keep-alive
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log('Received:', msg);
};
ws.onerror = (err) => console.error('WS error:', err);
ws.onclose = () => console.log('Disconnected');
import asyncio
import json
import websockets
async def main():
uri = 'wss://api.prex.world/ws'
async with websockets.connect(uri) as ws:
print('Connected')
# Public channel — no auth needed
await ws.send(json.dumps({
'type': 'subscribe',
'channel': 'spot:depth:DFUSDT',
}))
async def heartbeat():
while True:
await asyncio.sleep(30)
await ws.send(json.dumps({'type': 'ping'}))
asyncio.create_task(heartbeat())
async for message in ws:
msg = json.loads(message)
print('Received:', msg)
asyncio.run(main())
Message Format
Client → Server
type | Description | Auth required |
|---|---|---|
auth | Authenticate via EIP-712 signature or listenKey | — |
auth_token | Authenticate via JWT token | — |
subscribe | Subscribe to a channel (may include inline token) | Private channels only |
unsubscribe | Unsubscribe from a channel | No |
ping | Heartbeat | No |
Server → Client
type | Description |
|---|---|
auth_result | Authentication outcome (success: true/false) |
subscribed | Subscription confirmed |
unsubscribed | Unsubscription confirmed |
pong | Response to ping |
error | Error frame (code + message) |
spot_depth_snapshot | Full order-book snapshot (on subscribe to spot:depth:*) |
spot_depth_diff | Changed levels since last update |
spot_trade | Single public fill |
spot_ticker | 24 h rolling statistics |
spot_kline_snapshot | Latest candle (on subscribe to spot:kline:*) |
spot_kline_update | Running OHLCV update for the current interval |
spot_user_order | Order lifecycle event for the authenticated user |
spot_user_balance | Balance row change for the authenticated user |
All push frames are wrapped as:
{
"type": "spot_xxx",
"channel": "spot:depth:DFUSDT",
"data": { ... }
}
Authentication
Authentication is only required for private channels (spot:user:orders, spot:user:balances). Public market-data channels work without it.
Three methods are supported (identical to the perp WebSocket — see perp WebSocket general info for full payload shapes). The simplest is JWT:
JWT (auth_token)
{ "type": "auth_token", "token": "eyJhbGciOiJIUzI1..." }
Server response:
{ "type": "auth_result", "success": true, "message": null }
On failure:
{ "type": "auth_result", "success": false, "message": "Invalid or expired token" }
EIP-712 (auth)
{
"type": "auth",
"address": "0xYourWalletAddress",
"signature": "0x...",
"timestamp": 1778400000
}
timestamp is UNIX seconds, must be within 5 minutes of server time.
listenKey (auth)
{ "type": "auth", "listenKey": "a1b2c3d4e5f6..." }
Obtain a listenKey via POST /fapi/v1/listenKey. A long-running WebSocket connection acts as implicit keepalive — no periodic PUT /fapi/v1/listenKey needed while the socket is open.
Inline token on subscribe
{ "type": "subscribe", "channel": "spot:user:orders", "token": "eyJhbGciOiJIUzI1..." }
The server authenticates automatically before processing the subscription.
Channels at a Glance
| Channel | Auth | Snapshot on subscribe | Live updates |
|---|---|---|---|
spot:depth:{symbol} | — | yes — top 1 000 levels | diffs on every book mutation |
spot:trade:{symbol} | — | — | one push per public fill |
spot:ticker:{symbol} | — | yes — current 24 h row | on fill only |
spot:kline:{symbol}:{interval} | — | yes — latest candle | on every fill in the interval |
spot:user:orders | required | — | place / fill / cancel / reject |
spot:user:balances | required | — | per affected (token) row |
{symbol} is DFUSDT in MVP. {interval} ∈ 1m / 5m / 15m / 1h / 4h / 1d — see Enums.