◈   ⌘ api · Intermediate

Binance Futures User Data Stream with Python

Learn how to connect to Binance Futures User Data Stream using Python to track orders, positions, and account updates in real time.

Uncle Solieditor · voc · 18.05.2026 ·views 2
◈   Contents
  1. → What Is the Binance Futures User Data Stream?
  2. → Authentication: Getting a Listen Key
  3. → Connecting to the WebSocket Stream
  4. → Parsing the Three Core Event Types
  5. → Adding Reconnection Logic
  6. → Practical Use Cases for Trading Systems
  7. → Frequently Asked Questions
  8. → Conclusion

If you're building a trading bot or monitoring system on Binance Futures, polling the REST API every second is a rookie mistake. It's slow, burns your rate limits, and misses events between calls. The correct approach is the User Data Stream — a private WebSocket feed that pushes account updates, order fills, and position changes the instant they happen. This guide walks through setting it up in Python from scratch.

What Is the Binance Futures User Data Stream?

The User Data Stream is a private, authenticated WebSocket channel that Binance provides for each account. Unlike public market data streams (which anyone can subscribe to), this one is scoped to your account and delivers three critical event types: ACCOUNT_UPDATE (balance and position changes), ORDER_TRADE_UPDATE (order lifecycle events — new, filled, canceled, expired), and MARGIN_CALL (liquidation warnings). Compared to REST polling, the latency difference is dramatic — you get notified in milliseconds rather than waiting for your next scheduled request.

Binance Futures is the primary exchange where this pattern matters most, but similar private WebSocket feeds exist on Bybit and OKX for their perpetual futures markets. The implementation details differ, but the concept is the same — subscribe once, receive everything. This guide focuses on Binance USD-M Futures (usdfutures), which uses USDT-margined contracts.

Authentication: Getting a Listen Key

The User Data Stream uses a listen key — a temporary token you generate via REST and then pass to the WebSocket connection. Listen keys expire after 60 minutes, so your code must keep them alive by sending a keepalive ping every 30-50 minutes. Here's the flow: generate key → open WebSocket with the key → periodically extend it before it expires.

import requests
import time

API_KEY = "your_binance_api_key"
BASE_URL = "https://fapi.binance.com"

HEADERS = {"X-MBX-APIKEY": API_KEY}

def get_listen_key() -> str:
    resp = requests.post(
        f"{BASE_URL}/fapi/v1/listenKey",
        headers=HEADERS
    )
    resp.raise_for_status()
    return resp.json()["listenKey"]

def extend_listen_key(listen_key: str) -> None:
    resp = requests.put(
        f"{BASE_URL}/fapi/v1/listenKey",
        headers=HEADERS,
        params={"listenKey": listen_key}
    )
    resp.raise_for_status()

listen_key = get_listen_key()
print(f"Listen key: {listen_key}")
Never share your listen key. It grants read access to your account event stream and could reveal open positions, order sizes, and balance changes to anyone who has it.

Connecting to the WebSocket Stream

With a listen key in hand, connecting is straightforward. The WebSocket URL for Binance USD-M Futures is wss://fstream.binance.com/ws/{listenKey}. We'll use the websockets library — it's async-native and handles reconnection logic cleanly. Install it with pip install websockets requests.

import asyncio
import json
import websockets
import threading
import time
import requests

API_KEY = "your_binance_api_key"
BASE_URL = "https://fapi.binance.com"
WS_BASE = "wss://fstream.binance.com/ws"
HEADERS = {"X-MBX-APIKEY": API_KEY}

def get_listen_key() -> str:
    r = requests.post(f"{BASE_URL}/fapi/v1/listenKey", headers=HEADERS)
    r.raise_for_status()
    return r.json()["listenKey"]

def extend_listen_key(listen_key: str) -> None:
    requests.put(
        f"{BASE_URL}/fapi/v1/listenKey",
        headers=HEADERS,
        params={"listenKey": listen_key}
    )

def keepalive_loop(listen_key: str, interval: int = 1800) -> None:
    """Run in a background thread — extends key every 30 min."""
    while True:
        time.sleep(interval)
        extend_listen_key(listen_key)
        print("Listen key extended")

async def handle_message(msg: dict) -> None:
    event = msg.get("e")
    if event == "ORDER_TRADE_UPDATE":
        order = msg["o"]
        print(f"Order update: {order['s']} {order['S']} {order['X']} qty={order['q']} price={order['L']}")
    elif event == "ACCOUNT_UPDATE":
        for balance in msg["a"].get("B", []):
            print(f"Balance: {balance['a']} wallet={balance['wb']} cross={balance['cw']}")
        for pos in msg["a"].get("P", []):
            print(f"Position: {pos['s']} amt={pos['pa']} entry={pos['ep']}")
    elif event == "MARGIN_CALL":
        print(f"MARGIN CALL: {msg}")

async def stream(listen_key: str) -> None:
    url = f"{WS_BASE}/{listen_key}"
    async with websockets.connect(url, ping_interval=20) as ws:
        print(f"Connected to {url}")
        async for raw in ws:
            msg = json.loads(raw)
            await handle_message(msg)

if __name__ == "__main__":
    lk = get_listen_key()
    # keepalive runs in background thread
    t = threading.Thread(target=keepalive_loop, args=(lk,), daemon=True)
    t.start()
    asyncio.run(stream(lk))

The keepalive thread is critical. If you skip it, your listen key expires after 60 minutes and the WebSocket disconnects silently — your bot stops receiving fills without any obvious error. A 30-minute interval gives comfortable headroom.

Parsing the Three Core Event Types

Each event from the stream has a different payload structure. Understanding what each field means is essential before you build anything on top of this data.

ORDER_TRADE_UPDATE Key Fields
FieldKeyExample ValueMeaning
Symbolo.sBTCUSDTTrading pair
Sideo.SBUY / SELLOrder direction
Order Statuso.XFILLED / NEW / CANCELEDCurrent state
Original Qtyo.q0.01Total order quantity
Last Filled Qtyo.l0.01Qty filled in this event
Last Fill Priceo.L67500.00Price of last fill
Realized PnLo.rp12.50Realized PnL on close
Commissiono.n0.034Fee paid for this fill
Commission Asseto.NUSDTFee currency
ACCOUNT_UPDATE Key Fields
SectionKeyFieldMeaning
Balancea.B[].aAssetCurrency symbol (USDT, BNB)
Balancea.B[].wbWallet BalanceTotal wallet balance
Balancea.B[].cwCross Wallet BalanceAvailable in cross margin
Positiona.P[].sSymbolContract symbol
Positiona.P[].paPosition AmountCurrent position size
Positiona.P[].epEntry PriceAverage entry price
Positiona.P[].upUnrealized PnLCurrent floating PnL

For MARGIN_CALL events, the payload contains a list of positions approaching liquidation. In practice, you should treat this event as an emergency signal — log it, send an alert, and if your bot is risk-aware, consider reducing position size immediately. Platforms like VoiceOfChain use real-time position data from exactly these kinds of streams to surface whale-level margin pressure signals before they move markets.

Adding Reconnection Logic

Production code needs to handle disconnections gracefully. Network blips, Binance maintenance windows, or IP rate limits can drop your WebSocket. The pattern below wraps the connection in a retry loop with exponential backoff and regenerates the listen key on each reconnect — because the old key may have expired during the downtime.

import asyncio
import json
import websockets
import threading
import time
import requests
from websockets.exceptions import ConnectionClosed

API_KEY = "your_api_key"
BASE_URL = "https://fapi.binance.com"
WS_BASE = "wss://fstream.binance.com/ws"
HEADERS = {"X-MBX-APIKEY": API_KEY}

def get_listen_key() -> str:
    r = requests.post(f"{BASE_URL}/fapi/v1/listenKey", headers=HEADERS)
    r.raise_for_status()
    return r.json()["listenKey"]

async def handle_message(msg: dict) -> None:
    event = msg.get("e")
    if event == "ORDER_TRADE_UPDATE":
        o = msg["o"]
        if o["X"] == "FILLED":
            print(f"FILL {o['s']} {o['S']} qty={o['l']} @ {o['L']} pnl={o['rp']}")
    elif event == "ACCOUNT_UPDATE":
        reason = msg["a"]["m"]
        print(f"Account update reason: {reason}")
    elif event == "MARGIN_CALL":
        print(f"!!! MARGIN CALL: {json.dumps(msg, indent=2)}")

async def run_stream() -> None:
    retry_delay = 1
    while True:
        try:
            lk = get_listen_key()
            url = f"{WS_BASE}/{lk}"
            print(f"Connecting to stream...")
            async with websockets.connect(url, ping_interval=20) as ws:
                retry_delay = 1  # reset on successful connect
                async for raw in ws:
                    await handle_message(json.loads(raw))
        except (ConnectionClosed, Exception) as e:
            print(f"Stream error: {e}, retrying in {retry_delay}s")
            await asyncio.sleep(retry_delay)
            retry_delay = min(retry_delay * 2, 60)  # cap at 60s

if __name__ == "__main__":
    asyncio.run(run_stream())
Always regenerate the listen key on reconnect. Don't try to reuse the old key — it may have been invalidated. The extra REST call costs microseconds and prevents a hard-to-debug silent failure.

Practical Use Cases for Trading Systems

Once you have the stream running reliably, there are several high-value ways to use it in a real trading setup.

If you're running strategies on both Binance and Bybit, note that Bybit's equivalent is the Private WebSocket topic execution — the event schema differs but the architecture is identical. OKX uses a similar authenticated WebSocket with a separate login step. The patterns from this guide transfer directly; only the field names change.

Frequently Asked Questions

How long does a Binance listen key last before it expires?
Listen keys expire after 60 minutes of inactivity. You must send a PUT request to /fapi/v1/listenKey every 30-50 minutes to keep it alive. Most production implementations run a background thread that pings every 30 minutes.
Can I use the User Data Stream on Binance Spot and Futures at the same time?
Yes, but they are separate endpoints with separate listen keys. Spot uses api.binance.com/api/v3/userDataStream, while USD-M Futures uses fapi.binance.com/fapi/v1/listenKey. You need two connections running concurrently if you trade both.
What happens if I miss events while disconnected?
The stream does not replay missed events — there is no rewind mechanism. When your bot reconnects, immediately fetch current account state and open orders via REST to reconcile your internal state before resuming stream processing.
Is the User Data Stream rate limited?
The stream itself isn't rate limited, but the listen key generation endpoint counts against your API weight. You should only generate a new listen key when needed — on startup or after a reconnect — not repeatedly in a tight loop.
Why am I not receiving any events even though I'm connected?
The stream is event-driven — if there's no account activity (no orders, no fills), it stays silent. Test by placing a small order on Binance. Also verify your listen key is for the correct environment — testnet keys don't work on mainnet endpoints.
Does this work on Binance testnet?
Yes. Use testnet.binancefuture.com for REST and stream.binancefuture.com for WebSocket. You'll need testnet API keys from the Binance Futures testnet site — they're separate from production keys.

Conclusion

The Binance Futures User Data Stream is foundational infrastructure for any serious algorithmic trading setup. REST polling is fine for low-frequency strategies and one-off queries, but anything that needs to react to fills, position changes, or margin warnings in real time requires a WebSocket connection. The pattern — generate listen key, connect WebSocket, keepalive in background, reconnect with fresh key on failure — is simple enough to implement in an afternoon and robust enough to run in production for months. Combine it with real-time signal feeds like VoiceOfChain to correlate your own account activity with broader market order flow, and you have a genuinely powerful information advantage over traders flying blind on delayed data.

◈   more on this topic
◉ basics Mastering the ccxt library documentation for crypto traders ⌂ exchanges Mastering the Binance CCXT Library for Crypto Traders ⌬ bots Best Crypto Trading Bots 2025: Profitable AI-Powered Strategies