# Binance API user data stream: listenKey, subscribe, events

URL: https://fincodelabs.com/en/blog/binance-user-data-stream · Publisher: Fincode (Fincode Yazılım ve Teknoloji A.Ş.), Istanbul, Turkey · Category: Exchange APIs · Published: 2026-11-06 · Updated: 2026-11-06

Binance API user data stream: Spot listenKey retired (HTTP 410), userDataStream.subscribe, USD-M listenKey, events and Python tested on a local mock.

**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.

**Old Spot calls tested on 5 November 2026 with a made-up key and listenKey**

| 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](https://fincodelabs.com/en/blog/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.

**Old and current Spot flow, per the Spot documentation read 5 November 2026**

| 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](https://fincodelabs.com/en/blog/websocket-heartbeat-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.

**The two Spot methods, per the WebSocket API page and changelog read 5 November 2026**

| 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.

```python
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](https://fincodelabs.com/en/blog/binance-api-python-trading-bot).

## 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`.

**Spot events, per the User Data Stream page read 5 November 2026**

| 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.

**USD-M calls, per pages read 5 November 2026; each REST call has weight 1**

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

```python
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](https://fincodelabs.com/en/blog/binance-futures-testnet-api-key).

- **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_UPDATE` without saying whether `events` is 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 `/ws` URLs, which the notice said would end after 23 April 2026, still delivered `bookTicker` on 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.

1. Connect, subscribe and buffer events without acting on them.
2. Read open orders, fills since the last processed event, and balances or positions.
3. Merge, drop buffered events the snapshot includes, and allow new orders only once the view matches.

**Snapshot calls read 5 November 2026, with stated weights; USD-M paths gave 401, not 404, on the demo host**

| 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](https://fincodelabs.com/en/blog/position-reconciliation) and [crypto order management system](https://fincodelabs.com/en/blog/crypto-order-management-system). For integration work in general, see [exchange API integration](https://fincodelabs.com/en/services/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)](https://github.com/binance/binance-spot-api-docs/blob/master/user-data-stream.md)
- [Binance Spot API documentation: WebSocket API (general information, session and user data stream requests)](https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-api.md)
- [Binance Spot API: CHANGELOG (last updated 30 October 2026)](https://github.com/binance/binance-spot-api-docs/blob/master/CHANGELOG.md)
- [Binance Spot Testnet documentation: WebSocket API (subscribe parameters and filter)](https://github.com/binance/binance-spot-api-docs/blob/master/testnet/web-socket-api.md)
- [Binance Spot Testnet: CHANGELOG (filter added 28 October 2026)](https://github.com/binance/binance-spot-api-docs/blob/master/testnet/CHANGELOG.md)
- [Binance.US Help Center: Binance.US will retire the listenKey mechanism (article showing 23 September 2026)](https://support.binance.us/en/articles/17045412-binance-us-will-retire-the-listenkey-mechanism-for-user-data-stream-subscriptions)
- [Binance Spot API documentation: REST API (account endpoint weights)](https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md)
- [Binance Spot API documentation: Market Data Only URLs](https://github.com/binance/binance-spot-api-docs/blob/master/faqs/market_data_only.md)
- [Binance USD-M Futures: User Data Streams (last modified 30 October 2026)](https://developers.binance.com/en/docs/products/derivatives-trading-usds-futures/user-data-streams)
- [Binance USD-M Futures REST reference: user data streams (listenKey calls)](https://developers.binance.com/en/docs/catalog/core-trading-derivatives-trading-usd-s-m-futures/api/rest-api/user-data-streams)
- [Binance USD-M Futures: Important WebSocket Change Notice (last modified 30 October 2026)](https://developers.binance.com/en/docs/products/derivatives-trading-usds-futures/websocket-market-streams/Important-WebSocket-Change-Notice)
- [Binance USD-M Futures REST reference: trade endpoints (userTrades weight)](https://developers.binance.com/en/docs/catalog/core-trading-derivatives-trading-usd-s-m-futures/api/rest-api/trade)
- [Binance USD-M Futures: Change Log (entry of 6 November 2025 on conditional orders)](https://developers.binance.com/en/docs/products/derivatives-trading-usds-futures/change-log)
- [Binance Margin Trading: Listen Token User Data Stream (last modified 30 October 2026)](https://developers.binance.com/en/docs/products/margin-trading/listen-token-data-stream)
- [Capital Markets Law No. 6362, consolidated text, Article 99/A (Turkish)](https://www.mevzuat.gov.tr/mevzuatmetin/1.5.6362.pdf)

> 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](https://fincodelabs.com/en/editorial-policy).

---

Published by Fincode (Fincode Yazılım ve Teknoloji A.Ş.), a software company in Istanbul, Turkey: https://fincodelabs.com/en/blog/binance-user-data-stream

Cite as: Fincode, "Binance API user data stream: listenKey, subscribe, events", updated November 6, 2026, https://fincodelabs.com/en/blog/binance-user-data-stream
