Short answer: The Binance API user data stream pushes your own order, balance and position events over WebSocket. On Spot, the listenKey endpoints were retired on 20 February 2026 and returned HTTP 410 when tested on 5 November 2026; you now subscribe on the WebSocket API with userDataStream.subscribe or userDataStream.subscribe.signature. USD-M futures still uses a 60-minute listenKey from POST /fapi/v1/listenKey.
Facts come from Binance's Spot documentation (changelog last updated 30 October 2026) and its USD-M user data streams page (last modified the same day), read on 5 November 2026, when public behavior was tested with made-up keys. No API key was used, so signed code ran only against a local mock, with synthetic values.
This is a technical guide, not a recommendation to use Binance or any strategy. Automated trading can lose money, and availability depends on your jurisdiction and the platform's terms. Article 99/A of Capital Markets Law No. 6362 treats activity by foreign crypto-asset platforms directed at Turkish residents as unauthorized.
Binance listen key: why your Spot listener stopped
A Spot listenKey listener stopped because Binance retired those endpoints. Per the changelog read 5 November 2026, listenKey streams were deprecated on 7 April 2025, and on 21 January 2026 Binance announced that POST, PUT and DELETE /api/v3/userDataStream, plus userDataStream.start, .ping and .stop, would be unavailable from 07:00 UTC on 20 February 2026.
| Call | Result | Why it misleads |
|---|---|---|
POST, PUT, DELETE /api/v3/userDataStream on production, Spot Testnet and Demo Mode; POST /api/v1/userDataStream on production | HTTP 410 Gone with an HTML body from nginx | response.json() fails before the status is read |
wss://stream.binance.com:9443/ws/<key> and the Testnet stream host | Open for 100 seconds with 0 messages | The socket looks healthy |
userDataStream.start, .ping, .stop on the WebSocket API | 401 with -2015, Invalid API-key, IP, or permissions for action, then a close with code 1008 | Reads like a key problem |
An open socket is therefore not a working feed. Binance.US, a separate platform, targets its own listenKey retirement for Q4 2026, per its help center read 5 November 2026. Other refusals are in Binance API error codes.
From listenKey to userDataStream.subscribe: the migration table
The migration replaces a REST-managed key and a separate stream host with a subscription on one WebSocket API connection.
| Step | Old listenKey flow | Current flow |
|---|---|---|
| Start | POST /api/v3/userDataStream or userDataStream.start | session.logon then userDataStream.subscribe, or userDataStream.subscribe.signature |
| Handle | listenKey | subscriptionId |
| Host | wss://stream.binance.com:9443/ws/<key> | wss://ws-api.binance.com:443/ws-api/v3; Testnet wss://ws-api.testnet.binance.vision/ws-api/v3 |
| Keepalive | PUT or userDataStream.ping | None for the subscription; answer the server's ping frames, sent every 20 seconds; a connection lasts at most 24 hours |
| Stop | DELETE or userDataStream.stop | userDataStream.unsubscribe, all or by subscriptionId |
| Event shape | Bare {"e": ...} | Wrapped {"subscriptionId": 0, "event": {"e": ...}} |
| End of feed | listenKeyExpired | eventStreamTerminated, serverShutdown |
The host data-stream.binance.vision carries no user data, per its FAQ. Reconnect timing is in the WebSocket heartbeat and reconnect guide.
userDataStream.subscribe or subscribe.signature?
Use userDataStream.subscribe with an Ed25519 key and one logged-on account, and userDataStream.subscribe.signature with any key type or several accounts on one connection.
| Aspect | subscribe | subscribe.signature |
|---|---|---|
| Key type | Ed25519 only, through session.logon | Any type, per the 12 August 2025 changelog |
| Parameters | None (see the box below) | apiKey, timestamp, signature, optional recvWindow |
session.logout | Closes it | Leaves it open |
| Weight | 2 | 2 |
Limits are shared: one subscription per account on a connection, 1,000 active per session and 65,535 over a session's life. The listen token on Binance's Margin page, last modified 30 October 2026, is margin's successor to listen keys, not Spot's. Signing is covered in API signature generation.
Where the documents disagree. The production WebSocket API page, read 5 November 2026, lists apiKey as mandatory for userDataStream.subscribe; the Spot Testnet page, read the same day, shows no apiKey. The servers side with the Testnet page: without logon, production, Demo Mode and the Testnet all answered apiKey with -1104, Not all sent parameters were read, and no parameters with -1193, WebSocket session not authenticated. Send no parameters after logon; a logged-on subscribe could not be tested.
A Testnet-only filter
The Spot Testnet changelog of 28 October 2026 adds an optional filter by event type, symbol, asset or execution type, and says the Testnet is not always in sync with the live exchange. On 5 November 2026 the Testnet passed it through to its logon check (-1193), while production and Demo Mode rejected it with -1104, so keep it behind a setting.
Binance user data stream Python example
This client logs on, subscribes, separates replies from events and keeps order state from cumulative fields; it places no orders. For userDataStream.subscribe.signature, skip the logon and send signed() as its parameters.
import asyncio, base64, json, os, time
from decimal import Decimal
import websockets
from cryptography.hazmat.primitives.serialization import load_pem_private_key
URL = os.environ["WS_API_URL"]
API_KEY = os.environ["BINANCE_API_KEY"]
with open(os.environ["BINANCE_ED25519_PEM"], "rb") as fh:
PRIVATE = load_pem_private_key(fh.read(), password=None)
def signed():
p = {"apiKey": API_KEY, "timestamp": int(time.time() * 1000)}
payload = "&".join(f"{k}={p[k]}" for k in sorted(p))
return p | {"signature": base64.b64encode(PRIVATE.sign(payload.encode())).decode()}
class Resubscribe(Exception):
pass
class AccountView:
def __init__(self):
self.orders, self.seen = {}, set()
def apply(self, ev):
kind = ev.get("e")
if kind in ("eventStreamTerminated", "serverShutdown"):
raise Resubscribe(kind)
if kind == "executionReport":
order, exec_id = (ev["s"], ev["i"]), ev.get("I") # not "c": cancels change it
if exec_id not in (None, "") and (order, exec_id) in self.seen:
return f"duplicate execution {exec_id} ignored"
self.seen.add((order, exec_id))
filled, quote = Decimal(ev["z"]), Decimal(ev["Z"]) # cumulative: never sum "l"
avg = quote / filled if filled else None
self.orders[order] = (ev["X"], filled, avg)
return f"order {ev['i']} {ev['X']} filled {filled:f} avg {avg}"
return f"{kind} logged"
async def run(view):
async with websockets.connect(URL) as ws:
await ws.send(json.dumps({"id": "logon", "method": "session.logon", "params": signed()}))
async for raw in ws:
msg = json.loads(raw)
if "event" in msg:
print(view.apply(msg["event"]))
elif msg.get("status") == 200 and msg.get("id") == "logon":
await ws.send(json.dumps({"id": "sub", "method": "userDataStream.subscribe"}))
elif msg.get("status") == 200:
print("subscribed", msg.get("result"))
else: # -2015 also closes the connection
raise RuntimeError(msg.get("error", msg))
raise Resubscribe("connection closed")
if __name__ == "__main__":
try:
asyncio.run(run(AccountView()))
except (Resubscribe, websockets.ConnectionClosed) as why:
print("feed ended:", why, "-> reconnect, subscribe, reconcile")
Output against the local mock on 5 November 2026 (Python 3.11, websockets 13.1), all values invented:
subscribed {'subscriptionId': 0}
order 1 NEW filled 0.00000000 avg None
order 1 PARTIALLY_FILLED filled 0.40000000 avg 100
duplicate execution 2 ignored
order 1 FILLED filled 1.00000000 avg 100.6
order 2 CANCELED filled 0.00000000 avg None
outboundAccountPosition logged
someFutureEvent logged
feed ended: eventStreamTerminated -> reconnect, subscribe, reconcile
The mock verifies the Ed25519 signature, returns the live servers' errors and pushes documented event shapes, plus a repeated execution, a cancel whose c differs from the original ID in C, and a made-up event type; the signature method, a clean close and a dropped connection also ran there. It proves parsing and signing, not that Binance would accept the session: on the real Testnet and production, a made-up key got -2015 from session.logon and userDataStream.subscribe.signature. Average price is Z divided by z, per Binance, and the execution ID check covers repeats from overlapping connections, untested live. Placing orders is covered in the Binance API Python guide.
Spot user data stream events and what to read
Six account event types arrive on a Spot subscription, plus the connection-level serverShutdown, which carries no subscriptionId.
| Event | Sent when | Fields to use |
|---|---|---|
executionReport | An order changes | s symbol, i order ID, x execution type, X status, z and Z cumulative, I execution ID |
outboundAccountPosition | Any balance changes | B: asset, free and locked for the assets that may have changed |
balanceUpdate | Deposit, withdrawal or transfer between accounts | a, d delta, T clear time |
listStatus | An order list changes, in addition to executionReport | g list ID, L list order status, O members |
eventStreamTerminated | Unsubscribe, session.logout on a logon subscription, or listen token expiry | Resubscribe unless you asked for it |
externalLockUpdate | An external system locks or unlocks balance, for example as margin collateral | a, d, T |
Key orders by symbol and order ID, not c: on a cancel, the original client order ID is in C. Many fields are conditional, such as eR on expired orders, so treat an absent key as normal.
Binance futures user data stream: the listenKey still works
USD-M futures keeps the listenKey model: create it with POST, extend it with PUT, close it with DELETE and connect on the /private route. Futures use leverage, so losses are amplified and positions can be liquidated; availability varies by jurisdiction.
| Call | Effect |
|---|---|
POST /fapi/v1/listenKey | Creates a key valid for 60 minutes; with an active key, returns it and extends it |
PUT /fapi/v1/listenKey | Extends by 60 minutes; -1125 means the key is gone, so POST again |
DELETE /fapi/v1/listenKey | Closes the stream and invalidates the key |
wss://fstream.binance.com/private/ws/<listenKey> | The connection, valid for 24 hours |
import requests
def keep_alive(session, base, listen_key):
"""Run well inside the 60 minutes; session carries X-MBX-APIKEY. Returns (key, must_reconnect)."""
r = session.put(f"{base}/fapi/v1/listenKey", timeout=10)
is_json = r.headers.get("Content-Type", "").startswith("application/json")
if r.status_code >= 400 and is_json and r.json().get("code") == -1125: # the key is gone
r = session.post(f"{base}/fapi/v1/listenKey", timeout=10)
r.raise_for_status()
return r.json()["listenKey"], True # reconnect, then reconcile
r.raise_for_status()
return listen_key, False
Against a local HTTP mock it kept a live key, replaced a deleted one and raised HTTPError on an HTML 502 page. With a made-up key, production, demo-fapi.binance.com and testnet.binancefuture.com all answered 401 with -2015; test hosts are covered in the Binance Futures testnet guide.
- Timer. The REST reference suggests a keepalive about every 60 minutes, the key's whole lifetime; a shorter timer, such as 30 minutes, is this page's suggestion.
- URL. The Important WebSocket Change Notice uses
/private/ws?listenKey=...&events=ORDER_TRADE_UPDATE/ACCOUNT_UPDATEwithout saying whethereventsis required. It and the table's form both accepted a made-up key and stayed silent for 100 seconds on 5 November 2026. - Legacy path. Legacy
/wsURLs, which the notice said would end after 23 April 2026, still deliveredbookTickeron 5 November 2026.
The page lists ten events, among them ORDER_TRADE_UPDATE, ACCOUNT_UPDATE and listenKeyExpired. Per the change log of 6 November 2025, conditional orders such as STOP_MARKET moved to the Algo Service effective 9 December 2025 and report through ALGO_UPDATE.
What to reconcile after a gap in the user data stream
Treat any gap as lost events, because no replay method appears on the pages read.
- Connect, subscribe and buffer events without acting on them.
- Read open orders, fills since the last processed event, and balances or positions.
- Merge, drop buffered events the snapshot includes, and allow new orders only once the view matches.
| Check | Spot | USD-M |
|---|---|---|
| Open orders | GET /api/v3/openOrders (6 per symbol, 80 for all) or openOrders.status | GET /fapi/v1/openOrders |
| Fills in the gap | GET /api/v3/myTrades (20, or 5 with orderId) or myTrades | GET /fapi/v1/userTrades (5) |
| Balances, positions | GET /api/v3/account (20) or account.status | GET /fapi/v3/account, GET /fapi/v3/positionRisk |
The USD-M page warns that REST queries may be delayed in volatile markets, so re-read once before declaring a mismatch; see position reconciliation and crypto order management system. For integration work in general, see exchange API integration; naming Binance here does not mean Fincode integrates it or has built on it.
Frequently asked questions
Does the Binance listen key still work?
On Spot, no: the endpoints were retired on 20 February 2026 and returned HTTP 410 on 5 November 2026. On USD-M futures, yes: POST /fapi/v1/listenKey still issues a key valid for 60 minutes.
Why does my Binance WebSocket connect but receive no account events?
An open socket proves little: on 5 November 2026 a made-up listenKey stayed connected for 100 seconds with no messages on Spot and USD-M hosts. On Spot, move to the WebSocket API; on USD-M, check the key with a PUT.
Do I need an Ed25519 key for the user data stream?
Only for session.logon followed by userDataStream.subscribe. userDataStream.subscribe.signature works with any key type, per the Spot changelog of 12 August 2025.
Does the user data stream replay missed events?
No replay method appears on the pages read on 5 November 2026. After a gap, resubscribe and read open orders, trades and balances from the API.
Sources
- Binance Spot API documentation: User Data Stream (events) (opens in a new tab)
- Binance Spot API documentation: WebSocket API (general information, session and user data stream requests) (opens in a new tab)
- Binance Spot API: CHANGELOG (last updated 30 October 2026) (opens in a new tab)
- Binance Spot Testnet documentation: WebSocket API (subscribe parameters and filter) (opens in a new tab)
- Binance Spot Testnet: CHANGELOG (filter added 28 October 2026) (opens in a new tab)
- Binance.US Help Center: Binance.US will retire the listenKey mechanism (article showing 23 September 2026) (opens in a new tab)
- Binance Spot API documentation: REST API (account endpoint weights) (opens in a new tab)
- Binance Spot API documentation: Market Data Only URLs (opens in a new tab)
- Binance USD-M Futures: User Data Streams (last modified 30 October 2026) (opens in a new tab)
- Binance USD-M Futures REST reference: user data streams (listenKey calls) (opens in a new tab)
- Binance USD-M Futures: Important WebSocket Change Notice (last modified 30 October 2026) (opens in a new tab)
- Binance USD-M Futures REST reference: trade endpoints (userTrades weight) (opens in a new tab)
- Binance USD-M Futures: Change Log (entry of 6 November 2025 on conditional orders) (opens in a new tab)
- Binance Margin Trading: Listen Token User Data Stream (last modified 30 October 2026) (opens in a new tab)
- Capital Markets Law No. 6362, consolidated text, Article 99/A (Turkish) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.