Binance WebSocket Connection Limit: What Traders Must Know
Master Binance WebSocket connection limits, avoid disconnections, and build reliable real-time data feeds with working Python and JavaScript code examples.
Master Binance WebSocket connection limits, avoid disconnections, and build reliable real-time data feeds with working Python and JavaScript code examples.
If you've ever built a trading bot that suddenly goes blind — stops receiving price updates with no obvious error — there's a good chance you hit Binance's WebSocket connection limit. It's one of those invisible walls that takes down otherwise solid bots, and it catches developers off guard because Binance doesn't always scream at you when you cross the line. Understanding exactly how these limits work, and how to design around them, is the difference between a bot that runs for days versus one that silently dies at 3am.
Binance enforces WebSocket limits at multiple levels, and conflating them is a common source of confusion. Here's the breakdown as of current Binance Spot and Futures API documentation:
| Limit Type | Value | Scope |
|---|---|---|
| Max streams per connection | 1024 | Per single WebSocket connection |
| Max connections per IP | 300 | Spot market streams |
| Connection keepalive (listen key) | 60 minutes | User data streams |
| Ping interval requirement | Every 3 minutes | To avoid server-side timeout |
| Max messages per second | 5 | Outbound from client |
The 1024 streams-per-connection limit is the one that bites algo traders most often. Each symbol subscription — say, a trade stream for BTCUSDT — counts as one stream. Subscribe to 100 symbols across 5 stream types (trades, klines, depth, bookTicker, miniTicker) and you're at 500 streams on a single connection. Scale to 250 symbols and you need at least two WebSocket connections. The 300 connections-per-IP limit is generous enough that most single-machine deployments never touch it, but institutional setups or poorly designed bots that open a fresh connection per symbol will hit it fast.
User data streams (order updates, account balance changes) require a listen key that expires after 60 minutes. You MUST send a keepalive request every 30-60 minutes or your order fill notifications will stop silently. This is separate from the ping/pong mechanism.
Binance supports combined stream endpoints that let you subscribe to multiple streams over a single WebSocket connection. This is the correct approach for any production bot. Here's a working Python example using the combined stream endpoint:
import asyncio
import websockets
import json
# Combined stream endpoint — up to 1024 streams per connection
BINANCE_WS_BASE = "wss://stream.binance.com:9443/stream?streams="
symbols = ["btcusdt", "ethusdt", "solusdt", "bnbusdt"]
# Build combined stream subscription
streams = []
for symbol in symbols:
streams.append(f"{symbol}@trade") # Real-time trades
streams.append(f"{symbol}@bookTicker") # Best bid/ask
url = BINANCE_WS_BASE + "/".join(streams)
async def handle_message(data: dict):
stream_name = data.get("stream", "")
payload = data.get("data", {})
if "@trade" in stream_name:
symbol = payload["s"]
price = float(payload["p"])
qty = float(payload["q"])
print(f"[TRADE] {symbol}: {price:.4f} x {qty:.4f}")
elif "@bookTicker" in stream_name:
symbol = payload["s"]
bid = float(payload["b"])
ask = float(payload["a"])
spread = ask - bid
print(f"[BOOK] {symbol}: bid={bid:.4f} ask={ask:.4f} spread={spread:.6f}")
async def ping_loop(ws):
"""Keep connection alive — Binance closes after 24h or on missed pings."""
while True:
await asyncio.sleep(180) # Ping every 3 minutes
await ws.ping()
async def connect():
while True: # Reconnect loop
try:
async with websockets.connect(url, ping_interval=None) as ws:
print(f"Connected to {len(streams)} streams")
ping_task = asyncio.create_task(ping_loop(ws))
try:
async for message in ws:
data = json.loads(message)
await handle_message(data)
finally:
ping_task.cancel()
except websockets.exceptions.ConnectionClosed as e:
print(f"Connection closed: {e}. Reconnecting in 5s...")
await asyncio.sleep(5)
except Exception as e:
print(f"Unexpected error: {e}. Reconnecting in 10s...")
await asyncio.sleep(10)
asyncio.run(connect())
A few things worth noting in that code: the manual ping loop is intentional. The `ping_interval=None` parameter disables the websockets library's built-in ping because Binance's server responds to WebSocket protocol pings correctly, but some versions of the library create timing conflicts. Rolling your own every 180 seconds gives you full control. The outer reconnect loop is non-negotiable for any production system — network blips happen, and a bot that doesn't reconnect is just a timer counting down to failure.
If you're building a web-based dashboard or prefer Node.js for your trading infrastructure, here's the equivalent pattern using the `ws` library:
const WebSocket = require('ws');
const symbols = ['btcusdt', 'ethusdt', 'solusdt', 'bnbusdt'];
const streams = symbols.flatMap(s => [
`${s}@kline_1m`, // 1-minute candles
`${s}@bookTicker` // Best bid/ask
]);
const url = `wss://stream.binance.com:9443/stream?streams=${streams.join('/')}`;
let ws;
let pingInterval;
let reconnectTimeout;
function connect() {
ws = new WebSocket(url);
ws.on('open', () => {
console.log(`Connected: ${streams.length} streams active`);
// Send ping every 3 minutes to prevent server timeout
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 180_000);
});
ws.on('message', (raw) => {
try {
const { stream, data } = JSON.parse(raw);
if (stream.includes('@kline')) {
const k = data.k;
if (k.x) { // Only closed candles
console.log(`[KLINE CLOSED] ${data.s} O:${k.o} H:${k.h} L:${k.l} C:${k.c} V:${k.v}`);
}
}
if (stream.includes('@bookTicker')) {
const spread = (parseFloat(data.a) - parseFloat(data.b)).toFixed(6);
console.log(`[BOOK] ${data.s} spread: ${spread}`);
}
} catch (err) {
console.error('Parse error:', err.message);
}
});
ws.on('close', (code, reason) => {
console.warn(`Closed [${code}]: ${reason}. Reconnecting in 5s...`);
cleanup();
reconnectTimeout = setTimeout(connect, 5000);
});
ws.on('error', (err) => {
console.error('WebSocket error:', err.message);
// 'close' event will fire after 'error', triggering reconnect
});
}
function cleanup() {
clearInterval(pingInterval);
clearTimeout(reconnectTimeout);
}
connect();
Once you need to track more than 200-300 symbols simultaneously, you'll need a connection management strategy. The naive approach — one WebSocket per symbol — will exhaust your IP's connection budget almost instantly. The right approach is to batch symbols into connection pools, each handling up to 1024 streams.
import asyncio
import websockets
import json
from typing import List, Callable
MAX_STREAMS_PER_CONNECTION = 900 # Leave headroom below the 1024 hard limit
BINANCE_WS_BASE = "wss://stream.binance.com:9443/stream?streams="
def chunk_streams(streams: List[str], chunk_size: int) -> List[List[str]]:
return [streams[i:i+chunk_size] for i in range(0, len(streams), chunk_size)]
async def manage_connection(streams: List[str], callback: Callable, conn_id: int):
url = BINANCE_WS_BASE + "/".join(streams)
print(f"[Conn-{conn_id}] Managing {len(streams)} streams")
while True:
try:
async with websockets.connect(url, ping_interval=None) as ws:
async def ping():
while True:
await asyncio.sleep(180)
await ws.ping()
ping_task = asyncio.create_task(ping())
try:
async for msg in ws:
data = json.loads(msg)
await callback(data, conn_id)
finally:
ping_task.cancel()
except Exception as e:
print(f"[Conn-{conn_id}] Error: {e}. Restarting in 5s...")
await asyncio.sleep(5)
async def start_pool(all_symbols: List[str], stream_types: List[str], callback: Callable):
all_streams = [
f"{symbol}@{stream_type}"
for symbol in all_symbols
for stream_type in stream_types
]
batches = chunk_streams(all_streams, MAX_STREAMS_PER_CONNECTION)
print(f"Spawning {len(batches)} WebSocket connections for {len(all_streams)} total streams")
tasks = [
asyncio.create_task(manage_connection(batch, callback, i))
for i, batch in enumerate(batches)
]
await asyncio.gather(*tasks)
# Example usage
async def my_callback(data: dict, conn_id: int):
stream = data.get("stream", "")
payload = data.get("data", {})
print(f"[{conn_id}] {stream}: received update")
async def main():
symbols = [f"symbol{i}usdt" for i in range(500)] # 500 symbols
stream_types = ["trade", "bookTicker"] # 1000 total streams → 2 connections
await start_pool(symbols, stream_types, my_callback)
asyncio.run(main())
This connection pooling pattern scales cleanly. 500 symbols with 2 stream types each gives you 1000 streams, split across two connections. Add more symbols and the pool automatically grows. Platforms like VoiceOfChain use similar architectures to monitor hundreds of assets in real-time and deliver trading signals the moment market conditions shift — the underlying WebSocket infrastructure is what makes sub-second signal delivery possible.
Binance isn't the only game in town for WebSocket data, and understanding how other exchanges handle connections helps you design multi-exchange bots or make informed decisions about where to route your order flow.
| Exchange | Max Streams/Connection | Max Connections/IP | Auth Method |
|---|---|---|---|
| Binance | 1024 | 300 | Listen key via REST |
| Bybit | 10 topics | 500 | API key in subscription message |
| OKX | Unlimited topics | Not documented | Login message after connect |
| KuCoin | 300 subscriptions | Not documented | Token via REST |
| Bitget | 100 subscriptions | Not documented | Login message after connect |
| Gate.io | Not documented | Not documented | Auth message |
| Coinbase Advanced | Unlimited | Not documented | JWT or API key in message |
Bybit's limit of 10 topics per connection is notably stricter than Binance's 1024, which means a Bybit bot monitoring many symbols needs significantly more connections or a more aggressive stream-batching strategy. OKX and Coinbase Advanced Trade are more permissive in their documented limits. If you're running a multi-exchange strategy across Binance, Bybit, and OKX simultaneously, you'll want an abstraction layer that handles the reconnect and auth patterns for each exchange independently — they're different enough that a unified client becomes unwieldy.
The question of whether Binance is banned in China comes up often in trading communities, and the answer affects how you think about infrastructure placement. China's regulatory crackdown in 2021 explicitly prohibited cryptocurrency trading and exchange services for Chinese residents. Binance ceased operations for mainland Chinese users and geoblocks access from Chinese IP addresses. This means if you're running a trading bot on a server in mainland China — whether a VPS in Beijing or a local machine — your WebSocket connections to Binance's endpoints will either fail outright or be subject to unstable connectivity due to the Great Firewall's interference.
For developers or traders in China, or those running infrastructure with Chinese IP ranges, the practical implications are: your WebSocket connections will drop far more frequently than Binance's own limits would cause, you'll need proxies or VPN infrastructure (which creates its own latency and reliability issues), and you're operating in a legally grey zone regardless of technical workarounds. The standard advice from the algo trading community is to host trading infrastructure in Singapore, Tokyo, or Hong Kong — all jurisdictions with clear legal frameworks and low-latency routes to Binance's servers. Hong Kong specifically has developed its own regulated crypto framework separate from mainland China's ban.
Even outside China, hosting your trading bot geographically close to Binance's matching engine (Singapore or Tokyo) meaningfully reduces WebSocket latency. A few milliseconds matter for arbitrage strategies between Binance and Bybit or Binance and OKX.
A WebSocket connection that works in testing and fails silently in production is worse than one that fails loudly — at least you know to fix it. The patterns in this guide — combined streams, connection pooling, manual ping loops, outer reconnect logic, and listen key keepalives — are the minimum viable infrastructure for anything running with real money on Binance. Getting these right once means your data layer becomes a solved problem, and you can focus on what actually generates edge: the signal logic on top of it.
Whether you're building a standalone bot or integrating with signal platforms like VoiceOfChain that already handle the WebSocket layer for you, understanding the limits at this level makes you a better debugger and a more informed architect. When something breaks at 3am, you'll know exactly where to look.