User Data Stream (listenKey)
Description
Bots subscribing to private WebSocket channels (orders, balances, positions) authenticate the WebSocket connection with a listenKey issued by REST. The lifecycle mirrors Binance:
POST /fapi/v1/listenKey— issue (or refresh) the user's listenKey- Open the WebSocket and send
{"type":"auth","listenKey":"<key>"} - Subscribe to private channels after
auth_result.success === true - Optionally call
PUT /fapi/v1/listenKeyto extend TTL (the WebSocket connection itself also extends TTL implicitly while online) DELETE /fapi/v1/listenKeyto revoke
Repeated POST returns the same active key and refreshes its TTL — it does not mint a new one. This matches Binance behavior. The TTL is 3600 seconds.
POST /fapi/v1/listenKey
Issue or refresh the caller's listenKey.
HTTP Request
POST /fapi/v1/listenKey (HMAC SHA256)
Weight
1
Request Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| recvWindow | LONG | NO | See Endpoint Security Type |
| timestamp | LONG | YES | Timestamp |
Response Example
{
"listenKey": "a1b2c3d4e5f6g7h8i9j0klmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01"
}
The key is a 64-character alphanumeric string ([A-Za-z0-9]).
PUT /fapi/v1/listenKey
Extend the TTL of the caller's active listenKey by another 3600 seconds.
HTTP Request
PUT /fapi/v1/listenKey (HMAC SHA256)
Weight
1
Request Parameters
No body. The user's listenKey is implicit (one active key per user).
Response Example
{ "listenKey": "a1b2c3..." }
Errors
| Code | Message | Cause |
|---|---|---|
-1125 | This listenKey does not exist. | The caller has no active listenKey. Issue one via POST first. |
DELETE /fapi/v1/listenKey
Revoke the caller's active listenKey.
HTTP Request
DELETE /fapi/v1/listenKey (HMAC SHA256)
Weight
1
Request Parameters
No body.
Response Example
{}
Idempotent — returns {} even when the caller had no active key.
WebSocket Authentication
After obtaining a listenKey, connect to the standard WebSocket endpoint and send:
{ "type": "auth", "listenKey": "a1b2c3..." }
A successful auth returns:
{ "type": "auth_result", "success": true, "message": null }
After this the connection can subscribe to any private channel (orders, balances, positions, etc.) — see WebSocket General Info.
Each message routed against a listenKey-authenticated WebSocket connection refreshes the TTL on both Redis keys (fapi:listen_key:<key> and the reverse map). A bot that maintains a long-running WS does not need to call PUT /fapi/v1/listenKey explicitly — the TTL will roll forward as long as the connection is active. Calling PUT is still useful when the WS is closing for maintenance and the client wants to reconnect later with the same key.
Code Examples
cURL
API_KEY="your_api_key"
API_SECRET="your_api_secret"
TIMESTAMP=$(date +%s%3N)
QUERY_STRING="timestamp=${TIMESTAMP}"
SIGNATURE=$(echo -n "${QUERY_STRING}" | openssl dgst -sha256 -hmac "${API_SECRET}" | awk '{print $2}')
# Issue listenKey
curl -s -X POST -H "X-MBX-APIKEY: ${API_KEY}" \
"https://api.ztdx.io/fapi/v1/listenKey?${QUERY_STRING}&signature=${SIGNATURE}"
# Keep alive (run every ~50 minutes)
curl -s -X PUT -H "X-MBX-APIKEY: ${API_KEY}" \
"https://api.ztdx.io/fapi/v1/listenKey?${QUERY_STRING}&signature=${SIGNATURE}"
# Revoke
curl -s -X DELETE -H "X-MBX-APIKEY: ${API_KEY}" \
"https://api.ztdx.io/fapi/v1/listenKey?${QUERY_STRING}&signature=${SIGNATURE}"
Python
import time, hmac, hashlib, json, requests, websocket
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
BASE_URL = "https://api.ztdx.io"
WS_URL = "wss://api.ztdx.io/ws/"
def sign(qs: str) -> str:
return hmac.new(API_SECRET.encode(), qs.encode(), hashlib.sha256).hexdigest()
def signed_request(method: str, path: str) -> dict:
ts = int(time.time() * 1000)
qs = f"timestamp={ts}"
sig = sign(qs)
r = requests.request(
method,
f"{BASE_URL}{path}?{qs}&signature={sig}",
headers={"X-MBX-APIKEY": API_KEY},
)
r.raise_for_status()
return r.json()
# 1. Issue listenKey
listen_key = signed_request("POST", "/fapi/v1/listenKey")["listenKey"]
print(f"listenKey: {listen_key}")
# 2. Connect + auth
ws = websocket.WebSocket()
ws.connect(WS_URL)
ws.send(json.dumps({"type": "auth", "listenKey": listen_key}))
print(ws.recv()) # {"type":"auth_result","success":true,...}
# 3. Subscribe to private channels
ws.send(json.dumps({"type": "subscribe", "channel": "orders"}))
ws.send(json.dumps({"type": "subscribe", "channel": "balances"}))
# 4. Read events
while True:
print(ws.recv())