Binance API WebSocket: Real-Time Data for Crypto Traders
Learn how to use Binance API WebSocket streams for live price feeds, order book updates, and trade data with Python examples and practical tips.
Learn how to use Binance API WebSocket streams for live price feeds, order book updates, and trade data with Python examples and practical tips.
REST APIs are great for snapshots. But if you're trading seriously — running bots, building dashboards, or reacting to market moves in milliseconds — you need a persistent connection that pushes data to you the moment something changes. That's exactly what the Binance API WebSocket delivers. Instead of hammering endpoints every second and burning through rate limits, you open one connection and let Binance stream prices, order book changes, and trade executions directly to your code. This guide walks through everything: setup, authentication, spot vs. futures streams, rate limits, and real Python examples you can actually run.
The Binance WebSocket API is a persistent, full-duplex connection between your application and Binance's servers using the WebSocket protocol. Once connected, data flows continuously without you needing to ask for it. Compare that to REST polling — if you're checking BTC price every 500ms, you're making 120 requests per minute and risking hitting rate limits. With WebSocket, one connection delivers every trade tick as it happens, with sub-100ms latency in most cases.
Binance offers WebSocket streams for spot markets, futures (USDT-M and COIN-M), and options. Competing platforms like Bybit and OKX have their own WebSocket implementations, but Binance's documentation is among the most complete and the stream variety is unmatched — over a dozen stream types covering everything from individual symbol tickers to full order book depth updates.
Before writing a single line of code, check the Binance API WebSocket documentation at the official Binance developer portal. The base URL for spot WebSocket streams is wss://stream.binance.com:9443, and for futures (USDT-M) it's wss://fstream.binance.com. Public market data streams require no API key — you just connect and subscribe. User data streams (your orders and balances) need authentication, which we'll cover below.
Install the dependencies first:
pip install websocket-client requests python-dotenv
Here's a minimal working example connecting to the Binance spot WebSocket and receiving live BTC/USDT trade data:
import websocket
import json
SYMBOL = "btcusdt"
STREAM_URL = f"wss://stream.binance.com:9443/ws/{SYMBOL}@aggTrade"
def on_message(ws, message):
data = json.loads(message)
price = float(data['p'])
quantity = float(data['q'])
side = 'BUY' if not data['m'] else 'SELL'
print(f"{side} {quantity:.6f} BTC @ ${price:,.2f}")
def on_error(ws, error):
print(f"WebSocket error: {error}")
def on_close(ws, close_status_code, close_msg):
print(f"Connection closed: {close_status_code}")
def on_open(ws):
print(f"Connected to Binance WebSocket stream for {SYMBOL.upper()}")
if __name__ == "__main__":
ws = websocket.WebSocketApp(
STREAM_URL,
on_message=on_message,
on_error=on_error,
on_close=on_close,
on_open=on_open
)
ws.run_forever()
The 'm' field in aggTrade messages indicates whether the buyer is the market maker. If m=True, it was a sell order hitting the bid. If m=False, it was a buy order lifting the ask. This is how you determine trade direction from WebSocket data.
The Binance API WebSocket behaves slightly differently for spot and futures markets. The endpoints differ, and futures streams carry additional fields — funding rates, mark price, liquidation orders — that don't exist in spot. If you're trading perpetuals on Binance Futures (or comparing with similar products on Bybit or OKX), you'll want the futures-specific streams.
| Market | Base WebSocket URL | Example Stream |
|---|---|---|
| Spot | wss://stream.binance.com:9443/ws/ | btcusdt@aggTrade |
| USDT-M Futures | wss://fstream.binance.com/ws/ | btcusdt@aggTrade |
| COIN-M Futures | wss://dstream.binance.com/ws/ | btcusd_perp@aggTrade |
| Testnet Spot | wss://testnet.binance.vision/ws/ | btcusdt@aggTrade |
For futures, the mark price stream is particularly valuable — it shows you the index-based price used for liquidations, not just the last trade price. Here's how to subscribe to the Binance API WebSocket futures mark price stream:
import websocket
import json
# Binance USDT-M Futures mark price stream
STREAM_URL = "wss://fstream.binance.com/ws/btcusdt@markPrice@1s"
def on_message(ws, message):
data = json.loads(message)
mark_price = float(data['p'])
index_price = float(data['i'])
funding_rate = float(data['r'])
next_funding = data['T']
print(f"Mark: ${mark_price:,.2f} | Index: ${index_price:,.2f} | Funding: {funding_rate*100:.4f}%")
ws = websocket.WebSocketApp(
STREAM_URL,
on_message=on_message,
on_error=lambda ws, e: print(f"Error: {e}"),
on_close=lambda ws, c, m: print("Closed")
)
ws.run_forever()
Market data is public — anyone can connect. But to receive your own order fills, balance updates, and position changes over WebSocket, you need a user data stream. Authentication works differently here compared to REST: you first make a REST call to create a listen key, then connect to the WebSocket using that key. The listen key expires after 60 minutes unless you send a keepalive ping every 30 minutes.
import requests
import websocket
import json
import threading
import time
import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("BINANCE_API_KEY")
BASE_URL = "https://api.binance.com"
def get_listen_key():
"""Create a new user data stream listen key."""
headers = {"X-MBX-APIKEY": API_KEY}
response = requests.post(
f"{BASE_URL}/api/v3/userDataStream",
headers=headers
)
response.raise_for_status()
return response.json()["listenKey"]
def keepalive_listen_key(listen_key):
"""Extend listen key validity — call every 30 minutes."""
headers = {"X-MBX-APIKEY": API_KEY}
requests.put(
f"{BASE_URL}/api/v3/userDataStream",
headers=headers,
params={"listenKey": listen_key}
)
def on_message(ws, message):
data = json.loads(message)
event_type = data.get('e')
if event_type == 'executionReport':
symbol = data['s']
side = data['S'] # BUY or SELL
status = data['X'] # NEW, FILLED, CANCELED, etc.
price = data['p']
qty = data['q']
print(f"Order Update: {side} {qty} {symbol} @ {price} — {status}")
elif event_type == 'outboundAccountPosition':
balances = {b['a']: b['f'] for b in data['B'] if float(b['f']) > 0}
print(f"Balance Update: {balances}")
def start_user_data_stream():
listen_key = get_listen_key()
print(f"Listen key obtained: {listen_key[:8]}...")
# Keepalive thread — pings every 29 minutes
def keepalive_loop():
while True:
time.sleep(29 * 60)
keepalive_listen_key(listen_key)
print("Listen key refreshed")
threading.Thread(target=keepalive_loop, daemon=True).start()
ws_url = f"wss://stream.binance.com:9443/ws/{listen_key}"
ws = websocket.WebSocketApp(
ws_url,
on_message=on_message,
on_error=lambda ws, e: print(f"Error: {e}"),
on_close=lambda ws, c, m: print("User stream closed")
)
ws.run_forever()
if __name__ == "__main__":
start_user_data_stream()
Never hardcode your API key in source code. Load it from environment variables or a .env file. A leaked Binance API key with withdrawal permissions is a serious security risk — restrict API key permissions to read-only and trading only, and whitelist your IP in the Binance API management panel.
Understanding Binance API WebSocket limits is critical before going live. Binance enforces hard limits on how many streams you can open and how fast you can subscribe. Hitting these limits silently drops your connection or bans your IP — neither of which is obvious until your trading bot stops receiving data.
For robust production systems, implement automatic reconnection with exponential backoff. A simple reconnect loop catches dropped connections, but without backoff you risk hammering the server during an outage and triggering an IP ban. Here's a production-grade reconnect wrapper:
import websocket
import json
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class BinanceWebSocket:
def __init__(self, stream_url, on_data_callback):
self.stream_url = stream_url
self.on_data_callback = on_data_callback
self.reconnect_delay = 1 # seconds, doubles on each failure
self.max_reconnect_delay = 60
def on_message(self, ws, message):
self.reconnect_delay = 1 # Reset delay on successful message
data = json.loads(message)
self.on_data_callback(data)
def on_error(self, ws, error):
logger.error(f"WebSocket error: {error}")
def on_close(self, ws, close_status_code, close_msg):
logger.warning(f"Disconnected (code={close_status_code}). Reconnecting in {self.reconnect_delay}s...")
time.sleep(self.reconnect_delay)
self.reconnect_delay = min(self.reconnect_delay * 2, self.max_reconnect_delay)
self.connect()
def connect(self):
logger.info(f"Connecting to {self.stream_url}")
ws = websocket.WebSocketApp(
self.stream_url,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close
)
ws.run_forever(ping_interval=20, ping_timeout=10)
# Usage
def handle_price(data):
if 'p' in data:
print(f"BTC Price: ${float(data['p']):,.2f}")
client = BinanceWebSocket(
"wss://stream.binance.com:9443/ws/btcusdt@aggTrade",
handle_price
)
client.connect()
Raw WebSocket data is only useful if you do something with it. In practice, traders pipe Binance WebSocket streams into local order books, signal generation engines, or dashboards. Platforms like VoiceOfChain consume real-time price feeds from multiple exchanges to generate trading signals the moment market conditions change — that kind of low-latency reaction is only possible because WebSockets push data rather than waiting to be polled.
A common pattern is maintaining a local order book from the diff depth stream. You subscribe to the snapshot via REST, then apply incremental updates from the WebSocket to keep it current. This gives you a live view of market depth without the overhead of repeatedly fetching the full book. Traders using Binance, Bybit, or OKX often maintain parallel local books across exchanges to spot arbitrage opportunities or compare liquidity.
Another popular use case: feeding candle data from the Binance WebSocket kline stream into a technical indicator library like TA-Lib or pandas-ta. Every time a new candle closes, your code recalculates RSI, MACD, or whatever indicator you use, and fires an alert or places an order if conditions are met. This is the core loop of most retail algorithmic trading setups built on Binance.
When running multiple WebSocket connections — for example, monitoring 20 symbols across spot and futures — use asyncio with websockets library instead of threading. It handles hundreds of concurrent connections with far less memory overhead than threads, and is the standard approach for production trading systems.
The Binance API WebSocket is the foundation of any serious real-time trading system. Whether you're building a price alert bot, a live dashboard, or a fully automated strategy that reacts to order book changes in milliseconds, WebSocket connections are the only practical way to get there. REST polling simply can't compete on latency or efficiency at scale.
Start with the public aggTrade or bookTicker streams — no auth required, immediate feedback, and you'll understand the data structure within minutes. Add reconnection logic before you go anywhere near live money. When you're ready to act on signals rather than just display them, layer in the user data stream to track your own fills in real time. Tools like VoiceOfChain take this further by aggregating real-time feeds across markets to surface actionable trading signals — that's the practical end-state of what you build when you master the WebSocket layer. The infrastructure is simpler than it looks once you have working code in front of you.