跳到主要内容

WebSocket 基本信息 — 现货

ZTDX 现货 WebSocket 的连接指南、鉴权说明、通道列表及协议规则。

基础地址

环境WebSocket 地址
测试网 (Sepolia)wss://api-sepolia.p99.world/ws
主网(尚未部署)

现货 WebSocket 与合约 WebSocket 共用同一端点。单个连接可同时订阅任意组合的现货与合约通道。

连接说明

  • 所有消息均为 JSON 文本帧(RFC 6455)。
  • 每隔 30 秒发送一次 ping 以保持连接。
  • 单个连接支持多个并发订阅。
  • 未知现货通道会返回 INVALID_CHANNEL 错误;连接本身保持不断开。

连接示例

const ws = new WebSocket('wss://api-sepolia.p99.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');

消息格式

客户端 → 服务端

type描述需鉴权
auth通过 EIP-712 签名或 listenKey 进行鉴权
auth_token通过 JWT Token 进行鉴权
subscribe订阅通道(可附带内联 token仅私有通道
unsubscribe取消订阅通道
ping心跳

服务端 → 客户端

type描述
auth_result鉴权结果(success: true/false
subscribed订阅确认
unsubscribed取消订阅确认
pongping 的响应
error错误帧(code + message
spot_depth_snapshot完整订单簿快照(订阅 spot:depth:* 时推送)
spot_depth_diff自上次更新以来的变化档位(差分)
spot_trade单笔公开成交
spot_ticker24 小时滚动统计
spot_kline_snapshot最新 K 线(订阅 spot:kline:* 时推送)
spot_kline_update当前周期内的实时 OHLCV 更新
spot_user_order已鉴权用户的订单生命周期事件
spot_user_balance已鉴权用户的余额行变更

所有推送帧均按以下结构封装:

{
"type": "spot_xxx",
"channel": "spot:depth:DFUSDT",
"data": { ... }
}

鉴权

鉴权对私有通道(spot:user:ordersspot:user:balances)必须。公开行情通道无需鉴权。

支持三种方式(与合约 WebSocket 完全相同 — 完整载荷格式参见合约 WebSocket 基本信息)。最简单的是 JWT:

JWT(auth_token

{ "type": "auth_token", "token": "eyJhbGciOiJIUzI1..." }

服务端响应:

{ "type": "auth_result", "success": true, "message": null }

失败时:

{ "type": "auth_result", "success": false, "message": "Invalid or expired token" }

EIP-712 签名(auth

{
"type": "auth",
"address": "0xYourWalletAddress",
"signature": "0x...",
"timestamp": 1778400000
}

timestamp 为 UNIX ,须在服务器时间 5 分钟以内。

listenKey(auth

{ "type": "auth", "listenKey": "a1b2c3d4e5f6..." }

通过 POST /fapi/v1/listenKey 获取 listenKey。长连接的 WebSocket 等同于隐式 keepalive — socket 保持开启时无需主动调用 PUT /fapi/v1/listenKey

subscribe 中附带 Token

{ "type": "subscribe", "channel": "spot:user:orders", "token": "eyJhbGciOiJIUzI1..." }

服务端会在处理订阅前自动完成鉴权。

通道概览

通道需鉴权订阅时发快照实时更新
spot:depth:{symbol}是 — 前 1000 档每次订单簿变动推送差分
spot:trade:{symbol}每笔公开成交推送一条
spot:ticker:{symbol}是 — 当前 24 小时行情每次成交 + 每 60 秒重算
spot:kline:{symbol}:{interval}是 — 最新 K 线当前区间内每次成交后推送
spot:user:orders需鉴权下单 / 成交 / 撤单 / 拒单
spot:user:balances需鉴权每个受影响的 (token)

{symbol} 在 MVP 阶段为 DFUSDT{interval}1m / 5m / 15m / 1h / 4h / 1d — 参见 Enums

订阅 / 取消订阅 / Ping

订阅

{ "type": "subscribe", "channel": "spot:depth:DFUSDT" }

响应:

{ "type": "subscribed", "channel": "spot:depth:DFUSDT" }

若该通道有快照,将在 subscribed 确认后立即推送。

取消订阅

{ "type": "unsubscribe", "channel": "spot:depth:DFUSDT" }

响应:

{ "type": "unsubscribed", "channel": "spot:depth:DFUSDT" }

Ping / Pong

{ "type": "ping" }

响应:

{ "type": "pong" }

快照与实时同步顺序

spot:depth:* — 需要同步

  1. 发送订阅 → 服务端将 spot_depth_snapshot 加入队列,并开始缓冲实时 spot_depth_diff 事件。
  2. 用快照初始化本地订单簿。
  3. 丢弃所有 update_id_last <= snapshot.last_update_id 的差分。
  4. 依到达顺序应用剩余差分。每个差分的 update_id_first 应等于前一差分的 update_id_last + 1。若出现间隙,说明帧被丢弃 — 重新订阅以获取新快照。

spot:ticker:*spot:kline:* — 幂等,无需重新同步

每次推送均完整替换上一次的值。漏掉的推送会在数秒内被下一次推送覆盖。

spot:user:* — 先从 REST 初始化,再保持同步

不发送初始快照。鉴权后:

  1. 调用 GET /spot/orders 初始化本地订单状态。
  2. 调用 GET /spot/balances 初始化本地余额状态。
  3. 通过实时 spot_user_orderspot_user_balance 推送保持状态同步。

背压

每个连接有独立的发送队列,上限为 256 帧。若客户端消费速度过慢,队列溢出时帧将被静默丢弃。

客户端侧指标:深度差分的 update_id_first / update_id_last 出现间隙,说明帧被丢弃 — 重新订阅深度通道以重新同步。

服务端侧指标:内部广播通道溢出时,服务端日志中会出现 lagged 告警。

错误

{ "type": "error", "code": "INVALID_CHANNEL", "message": "Unknown channel: spot:wat" }
错误码触发条件
INVALID_CHANNEL未知通道名(如 spot:depths:DFUSDT — 注意拼写错误)。
AUTH_REQUIRED未鉴权即订阅 spot:user:* 通道。
INVALID_MESSAGE帧无法解析为有效的客户端消息。

REST 级别错误码另见错误码