WebSocket¶
Two WebSocket endpoints for different use cases.
Comparison¶
REST /ws (:22222) |
Core WS (--websocket-port :44444) |
|
|---|---|---|
| Protocol | JSON frames (base64(body)) |
FTAPI binary (44-byte header + protobuf body) |
| Purpose | Push-only (server → client) | Full-duplex (Futu SDK compatible) |
| Client | Browser / websocat / scripts |
Official futu-api SDK (over WS transport) |
| Auth | ?token= or Authorization: Bearer on handshake |
same |
| Per-message scope | — | Yes (v1.0+, checked by proto_id) |
REST /ws — push only¶
For browser frontends and lightweight scripts.
// Browser (WebSocket API can't set headers, use ?token=)
const ws = new WebSocket('ws://localhost:22222/ws?token=fc_xxxx...')
ws.onmessage = (ev) => {
const event = JSON.parse(ev.data)
// event: { type: 'quote' | 'trade' | 'notify',
// proto_id, sec_key?, sub_type?, acc_id?, body_b64 }
const body = atob(event.body_b64) // protobuf bytes
// ... decode body
}
v0.9+: the server filters pushes by the client's scope:
qot:read-only key → receivesquote+notifyonly, notradeqot:read + acc:read→ receives all three
Core WS — full-duplex binary¶
Use the futu-api SDK's WebSocket transport, or a raw client like
tokio-tungstenite.
Handshake: HTTP upgrade carries ?token= or Authorization: Bearer.
Missing qot:read scope → HTTP 403 with JSON error.
Per-message scope gate (v1.0+): every message is checked against the
proto_id's required scope; failures are dropped silently (no
response). Clients experience this as "request timeout / subscription
not taking effect".
| proto_id | scope |
|---|---|
1xxx system |
none |
3xxx quote + subscribe |
qot:read |
2005 / 2202 / 2205 / 2237 trade write |
trade:real |
Other 2xxx account read + subscribe |
acc:read |
Troubleshooting¶
| Symptom | Cause |
|---|---|
| Handshake 401 | Bearer / ?token typo, or key not in keys.json |
| Handshake 403 | Scope lacks qot:read |
| Subscribe OK but no pushes | Has qot:read but not acc:read (trade pushes filtered out) |
| Some commands don't respond | Per-message scope gate dropped them (check audit log) |
key revoked |
Key removed via remove-key + SIGHUP |
Audit + observability¶
- Both successful and failed handshakes are written to the audit JSONL (
iface=ws) - Filtered pushes increment
futu_ws_filtered_pushes_total{required_scope, key_id} - Handshake auth events increment
futu_auth_events_total{iface="ws"}
See Audit & observability for details.