User Data Streams
Type: USER-DATA
WS auth → positions | orders | balance | unified_account | points | vip_tier
Status: Implemented. Binance equivalent:
POST /fapi/v1/listenKey+wss://…/ws/<listenKey>(ACCOUNT_UPDATE,ORDER_TRADE_UPDATE,listenKeyExpired). On Sparky the listenKey is sent as anauthmessage on the ordinary/wssocket.
1. Get a listenKey
POST /fapi/v1/listenKey (HMAC-signed) → { "listenKey": "…64 chars…" }
See listenKey. One active key per user; TTL 60 min, refreshed automatically while a socket authenticated with it stays open.
2. Authenticate the socket
{ "type": "auth", "listenKey": "a1b2c3d4…" }
→ { "type": "auth_result", "success": true, "message": null }
Alternatives for front ends: { "type": "auth", "token": "<JWT>" } or { "type": "auth_token", "token": "<JWT>" }. A wallet-signature form (address + signature + timestamp) also exists.
An unknown or expired key answers { "type": "auth_result", "success": false, "message": "Invalid or expired listenKey" }.
3. Subscribe to private channels
{ "type": "subscribe", "channel": "positions" }
{ "type": "subscribe", "channel": "orders" }
{ "type": "subscribe", "channel": "balance" }
Subscribing before auth → { "type": "error", "code": "AUTH_REQUIRED" }.
positions
Snapshot of every open position on subscribe, then event-driven updates and a periodic re-push every 5 s.
{
"type": "position",
"id": "uuid",
"symbol": "BTCUSDT",
"side": "long",
"size": "500",
"entry_price": "50000",
"mark_price": "51000",
"liquidation_price": "45000",
"unrealized_pnl": "10",
"leverage": 10,
"margin": "50",
"updated_at": 1741298500000,
"event": "updated"
}
orders
Snapshot of open orders on subscribe (type: "order"), then one order_update per state change:
{ "type": "order", "id": "uuid", "symbol": "BTCUSDT", "side": "buy", "order_type": "limit", "price": "50000", "amount": "0.01", "filled_amount": "0", "status": "open", "updated_at": 1741298500000 }
{ "channel": "orders", "type": "order_update", "data": { "order_id": "uuid", "status": "filled", "...": "native order object" } }
balance
Snapshot per token on subscribe:
{ "type": "balance", "token": "USDT", "symbol": "USDT", "available": "850.00", "frozen": "150.00", "total": "1000.00" }
Balance deltas are published under the balances channel name ({ "channel": "balances", "type": "balance_update", "data": {...} }); the subscribe validator currently only accepts balance, so delta delivery should be treated as best-effort and reconciled with GET /fapi/v2/balance.
unified_account, points, vip_tier
Event envelopes for the web app: { "channel": "<name>", "event": "...", "data": {...} } (or "type": "vip_tier_changed"). Shapes follow the native API models and are not yet frozen for SDK use.
Field conventions
Private channel payloads use the native API conventions (snake_case, lowercase buy/sell, long/short, native order statuses open / partially_filled / filled / cancelled), not Binance's ORDER_TRADE_UPDATE shape. Map them in your client.