Skip to main content

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:

  1. POST /fapi/v1/listenKey — issue (or refresh) the user's listenKey
  2. Open the WebSocket and send {"type":"auth","listenKey":"<key>"}
  3. Subscribe to private channels after auth_result.success === true
  4. Optionally call PUT /fapi/v1/listenKey to extend TTL (the WebSocket connection itself also extends TTL implicitly while online)
  5. DELETE /fapi/v1/listenKey to revoke
One active listenKey per user

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

NameTypeRequiredDescription
recvWindowLONGNOSee Endpoint Security Type
timestampLONGYESTimestamp

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

CodeMessageCause
-1125This 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.

WS keeps the listenKey alive

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())