API Integration

How to Subscribe to Binance WebSocket Market Data? Single vs. Combined Stream Code

Complete Binance WebSocket real-time market data solution: Single/combined stream URLs, dynamic subscription management, reconnection logic, heartbeat, stream types, and order book depth merging algorithms. Includes Python asyncio and Node.js code.

The optimal solution for Binance WebSocket real-time market data is using the wss://stream.binance.com:9443/stream?streams= combined stream endpoint. A single connection can subscribe to up to 1024 streams with a message latency of less than 50ms and almost zero weight consumption. This article provides complete Python and Node.js code for single streams, combined streams, dynamic subscriptions, local order book depth merging, and reconnection logic—all ready to run. Before you begin, if you haven't registered a Binance account yet, you can visit the Binance Official gateway or complete your account opening via Free Registration.

1. WebSocket Endpoints Overview

Category URL Purpose
Spot Single wss://stream.binance.com:9443/ws/{stream} Single stream only
Spot Combined wss://stream.binance.com:9443/stream?streams={s1/s2/...} Multiple streams; includes stream name in message
Spot Backup wss://data-stream.binance.vision/ws/{stream} Official backup line
Futures (USD-M) wss://fstream.binance.com/stream?streams= Perpetual Futures
Futures (COIN-M) wss://dstream.binance.com/stream?streams= Coin-margined Futures
Testnet wss://stream.testnet.binance.vision/ws Testnet environment

Connection Limits: Maximum 300 WebSocket connections per IP. A single connection can subscribe to up to 1024 streams. Connections are automatically disconnected every 24 hours, so clients must implement reconnection logic.

2. Stream Type List

Binance provides 11 primary market data streams:

Stream Format Update Speed Description
aggTrade {symbol}@aggTrade Real-time Aggregated trades
trade {symbol}@trade Real-time Individual raw trades
kline_1m {symbol}@kline_1m 1s K-line candles (1m/5m/1h/...)
miniTicker {symbol}@miniTicker 1000ms Compact ticker statistics
ticker {symbol}@ticker 1000ms Full 24h ticker statistics
bookTicker {symbol}@bookTicker Real-time Best bid/ask price and quantity
depth {symbol}@depth 1000ms Full depth (20/50/100 levels)
depth@100ms {symbol}@depth@100ms 100ms Depth diff (incremental updates)
!miniTicker@arr !miniTicker@arr 1000ms Compact ticker for all symbols
!ticker@arr !ticker@arr 1000ms Full ticker for all symbols
!bookTicker !bookTicker Real-time Best bid/ask for all symbols

Naming Convention: All lowercase separated by @, e.g., btcusdt@ticker instead of BTCUSDT@ticker.

3. Python asyncio Single Stream Subscription

import asyncio, json
import websockets

async def subscribe_ticker(symbol: str):
    url = f"wss://stream.binance.com:9443/ws/{symbol.lower()}@ticker"
    async with websockets.connect(url, ping_interval=180) as ws:
        async for message in ws:
            data = json.loads(message)
            print(f"{data['s']}: Last Price {data['c']}, "
                  f"24h Change {data['P']}%, "
                  f"Volume {data['v']}")

asyncio.run(subscribe_ticker("BTCUSDT"))

Example output:

BTCUSDT: Last Price 68420.12, 24h Change 2.35%, Volume 45231.2
BTCUSDT: Last Price 68421.00, 24h Change 2.36%, Volume 45231.8

4. Combined Stream for Multiple Trading Pairs

One connection to subscribe to tickers for 10 pairs + K-lines for 5 pairs:

import asyncio, json
import websockets

STREAMS = [
    "btcusdt@ticker", "ethusdt@ticker", "bnbusdt@ticker",
    "solusdt@ticker", "xrpusdt@ticker", "adausdt@ticker",
    "dogeusdt@ticker", "maticusdt@ticker", "dotusdt@ticker",
    "avaxusdt@ticker",
    "btcusdt@kline_1m", "ethusdt@kline_1m",
    "bnbusdt@kline_1m", "solusdt@kline_1m", "xrpusdt@kline_1m"
]

async def combined_stream():
    streams = "/".join(STREAMS)
    url = f"wss://stream.binance.com:9443/stream?streams={streams}"
    async with websockets.connect(url, ping_interval=180) as ws:
        async for message in ws:
            payload = json.loads(message)
            stream = payload["stream"]
            data = payload["data"]
            if "@ticker" in stream:
                print(f"[{stream}] {data['s']}: {data['c']}")
            elif "@kline" in stream:
                k = data["k"]
                if k["x"]:  # Print only when K-line closes
                    print(f"[{stream}] {k['s']} {k['i']} "
                          f"O:{k['o']} H:{k['h']} L:{k['l']} C:{k['c']}")

asyncio.run(combined_stream())

5. Dynamic Subscribe/Unsubscribe (SUBSCRIBE Method)

After the connection is established, you can dynamically add or remove subscriptions via JSON messages:

import asyncio, json
import websockets

async def dynamic_sub():
    url = "wss://stream.binance.com:9443/ws"
    async with websockets.connect(url) as ws:
        # Subscribe to bookTicker for BTC and ETH
        await ws.send(json.dumps({
            "method": "SUBSCRIBE",
            "params": ["btcusdt@bookTicker", "ethusdt@bookTicker"],
            "id": 1
        }))

        count = 0
        async for msg in ws:
            data = json.loads(msg)
            if "result" in data:
                print(f"Subscription confirmed: {data}")
                continue
            count += 1
            print(f"{data['s']} Best Bid {data['b']} Best Ask {data['a']}")

            # After 50 messages, unsubscribe ETH and keep only BTC
            if count == 50:
                await ws.send(json.dumps({
                    "method": "UNSUBSCRIBE",
                    "params": ["ethusdt@bookTicker"],
                    "id": 2
                }))
                print("ETH subscription cancelled")

asyncio.run(dynamic_sub())

6. Depth Increment Merging (Maintaining Local Order Book)

High-frequency strategies often require a local order book at the matching engine level. The process is:

  1. Subscribe to the @depth@100ms diff stream and buffer messages.
  2. Make a REST call to GET /api/v3/depth?symbol=X&limit=1000 to fetch a snapshot.
  3. Discard buffered messages where u < lastUpdateId.
  4. Apply the first message where U <= lastUpdateId+1 <= u.
  5. Apply subsequent messages directly; the pu of the new message must equal the u of the previous one.
import asyncio, json, aiohttp
import websockets
from sortedcontainers import SortedDict

class OrderBook:
    def __init__(self, symbol):
        self.symbol = symbol.lower()
        self.bids = SortedDict()  # Price -> Quantity
        self.asks = SortedDict()
        self.last_update_id = 0
        self.buffer = []

    def apply_update(self, bids, asks):
        for price, qty in bids:
            price, qty = float(price), float(qty)
            if qty == 0:
                self.bids.pop(price, None)
            else:
                self.bids[price] = qty
        for price, qty in asks:
            price, qty = float(price), float(qty)
            if qty == 0:
                self.asks.pop(price, None)
            else:
                self.asks[price] = qty

    async def load_snapshot(self):
        async with aiohttp.ClientSession() as s:
            url = f"https://api.binance.com/api/v3/depth?symbol={self.symbol.upper()}&limit=1000"
            async with s.get(url) as r:
                snap = await r.json()
                self.last_update_id = snap["lastUpdateId"]
                self.apply_update(snap["bids"], snap["asks"])

    async def run(self):
        url = f"wss://stream.binance.com:9443/ws/{self.symbol}@depth@100ms"
        async with websockets.connect(url) as ws:
            # Step 1: Collect diffs for a short period
            asyncio.create_task(self._collect(ws))
            await asyncio.sleep(1)
            # Step 2: Fetch snapshot
            await self.load_snapshot()
            # Step 3: Apply valid diffs from buffer
            for diff in self.buffer:
                if diff["u"] < self.last_update_id:
                    continue
                self.apply_update(diff["b"], diff["a"])
                self.last_update_id = diff["u"]
            self.buffer = None
            # Step 4: Apply new messages directly
            async for msg in ws:
                diff = json.loads(msg)
                self.apply_update(diff["b"], diff["a"])
                self.last_update_id = diff["u"]
                best_bid = self.bids.keys()[-1] if self.bids else 0
                best_ask = self.asks.keys()[0] if self.asks else 0
                print(f"Best Bid {best_bid} Best Ask {best_ask} Spread {best_ask-best_bid:.2f}")

    async def _collect(self, ws):
        if self.buffer is None:
            return
        async for msg in ws:
            if self.buffer is None:
                break
            self.buffer.append(json.loads(msg))

ob = OrderBook("BTCUSDT")
asyncio.run(ob.run())

7. Reconnection and Heartbeat

Binance WebSocket requires clients to respond to a ping within 3 minutes, and connections are forced closed after 24 hours:

import asyncio, json, websockets, logging

async def resilient_ws(url, on_message, max_retries=1000):
    retry = 0
    while retry < max_retries:
        try:
            async with websockets.connect(
                url,
                ping_interval=180,  # Ping every 3 minutes
                ping_timeout=10,
                close_timeout=5
            ) as ws:
                retry = 0  # Reset retry on success
                async for message in ws:
                    await on_message(json.loads(message))
        except (websockets.ConnectionClosed, asyncio.TimeoutError) as e:
            retry += 1
            wait = min(2 ** retry, 60)  # Exponential backoff, max 60s
            logging.warning(f"WS Disconnected {e}, reconnecting in {wait}s (Attempt {retry})")
            await asyncio.sleep(wait)

8. Node.js WebSocket Example

const WebSocket = require('ws');

const streams = ['btcusdt@bookTicker', 'ethusdt@bookTicker'].join('/');
const url = `wss://stream.binance.com:9443/stream?streams=${streams}`;

function connect() {
  const ws = new WebSocket(url);
  ws.on('open', () => console.log('Connection successful'));
  ws.on('message', (raw) => {
    const { stream, data } = JSON.parse(raw);
    console.log(`${data.s} Best Bid ${data.b} Best Ask ${data.a}`);
  });
  ws.on('close', () => {
    console.log('Disconnected, reconnecting in 3s');
    setTimeout(connect, 3000);
  });
  ws.on('error', (err) => console.error('Error', err.message));
}

connect();

9. Frequently Asked Questions (FAQ)

Q1: Does WebSocket consume weight limits?

A: Establishing a connection consumes 2 weight units; subsequent message pushes consume zero weight. However, connections exceeding 300 per IP will be rejected.

Q2: Why am I not receiving any messages after subscribing?

A: Three common reasons: 1) Stream names must be all lowercase (e.g., btcusdt, not BTCUSDT); 2) Trading pair format is wrong (e.g., using BTC-USDT instead of BTCUSDT); 3) Subscription messages must be sent to the wss://.../ws endpoint, not the /stream endpoint.

Q3: What is the difference between @depth and @depth@100ms?

A: @depth pushes a full snapshot (20 levels) every 1000ms. @depth@100ms pushes diffs (incremental updates) every 100ms. Use @100ms for maintaining a local order book; @depth is sufficient for display purposes.

Q4: Can I place orders via WebSocket?

A: Yes. Binance provides a WebSocket API (wss://ws-api.binance.com:443/ws-api/v3) for signed order placement, which has 30-50ms lower latency than REST. However, since the ecosystem is less mature, most strategies still use REST for orders and WS for listening.

Q5: How do I handle the 24-hour automatic disconnection seamlessly?

A: Dual Connection Rolling Strategy: At the 23rd hour, establish a new connection B. Wait for B to receive messages stably before closing the old connection A to avoid data loss. Production-grade frameworks like python-binance have this logic built-in.

After exploring the WebSocket solution, return to the Categories page to view more SDK tutorials in the "API Integration" category.

Keep reading

Still have Binance questions? Head back to the category page for more tutorials on the same topic.

Categories

Related tutorials

How to Apply for Binance API? Common Guide for Key Generation and Signatures 2026-04-14 How to Use Binance Spot API? Executable Code from Zero to Your First Order 2026-04-14 What are the Differences Between Binance Futures and Spot APIs? Endpoint, Parameter, and Weight Comparison 2026-04-14 Will My IP Get Banned? Detailed Explanation of Binance API Rate Limits and Weights 2026-04-14