KuCoin API WebSocket: Real-Time Trading Data Guide
Master KuCoin's WebSocket API to stream live prices, order books, and trade data in real time. Includes Python code for connection, authentication, and resilient reconnection logic.
Master KuCoin's WebSocket API to stream live prices, order books, and trade data in real time. Includes Python code for connection, authentication, and resilient reconnection logic.
Real-time market data is the foundation of any serious trading strategy. REST APIs are fine for placing orders or fetching historical candles, but polling for live prices means hammering servers hundreds of times per minute — most responses returning identical data. KuCoin's WebSocket API solves this: one persistent connection delivers a continuous stream of ticks, order book changes, and trade events with millisecond latency. Whether you're building an algorithmic bot, a custom dashboard, or integrating alerts from a platform like VoiceOfChain, the KuCoin WebSocket API is the right tool for the job.
REST is request-response: you ask, the server answers, the connection closes. For a bot tracking 20 pairs, polling every 100ms generates thousands of HTTP requests per minute. WebSocket keeps the connection open and the server pushes data to you the instant it changes. On fast-moving assets like BTC or ETH, the latency difference matters: REST polling introduces 100–500ms delay even in ideal conditions, while a properly configured WebSocket delivers updates in under 10ms. Exchanges like Binance, Bybit, and OKX all offer WebSocket feeds for this reason — and KuCoin's implementation is one of the more feature-rich among them, supporting up to 100 topic subscriptions per connection.
Unlike Binance where you can connect directly, KuCoin requires a two-step setup. First, you call a REST endpoint to get a temporary connection token — this token contains the WebSocket server URL and keepalive parameters. Second, you append that token to the WebSocket URL and connect. For public data like prices and order books, no API key is needed at this stage. The kucoin websocket api documentation describes this token-based approach as a way to load-balance connections across their server fleet.
import asyncio
import websockets
import json
import requests
import uuid
def get_ws_token():
url = 'https://api.kucoin.com/api/v1/bullet-public'
response = requests.post(url)
data = response.json()
token = data['data']['token']
endpoint = data['data']['instanceServers'][0]['endpoint']
ping_interval = data['data']['instanceServers'][0]['pingInterval'] # milliseconds
return token, endpoint, ping_interval
async def ping_loop(ws, interval_sec):
while True:
await asyncio.sleep(interval_sec)
await ws.send(json.dumps({'id': str(uuid.uuid4()), 'type': 'ping'}))
async def listen():
token, endpoint, ping_ms = get_ws_token()
connect_url = endpoint + '?token=' + token + '&connectId=' + str(uuid.uuid4())
async with websockets.connect(connect_url) as ws:
welcome = json.loads(await ws.recv())
print('Connected:', welcome['type']) # prints: welcome
asyncio.create_task(ping_loop(ws, ping_ms // 1000))
sub = {
'id': str(uuid.uuid4()),
'type': 'subscribe',
'topic': '/market/ticker:BTC-USDT',
'privateChannel': False,
'response': True
}
await ws.send(json.dumps(sub))
async for msg in ws:
data = json.loads(msg)
if data.get('type') == 'message':
print(data['data'])
asyncio.run(listen())
KuCoin's server includes a pingInterval value in the connection response (typically 18000ms). You must send a ping message within that window or the server silently closes the connection. This is the single most common reason WebSocket connections drop unexpectedly — don't skip the ping loop.
Every subscription uses a topic string in the format /channel/type:SYMBOL. You can subscribe to multiple symbols in a single message by comma-separating them in the topic field — far more efficient than separate subscriptions. KuCoin offers channels for everything from individual tickers to full order book depth. Platforms like Bybit and OKX follow a similar multiplexing pattern, so if you've worked with their WebSocket APIs the structure will feel familiar.
| Topic | Data Delivered | Common Use Case |
|---|---|---|
| /market/ticker:BTC-USDT | Price, best bid/ask, 24h volume | Price alerts, dashboards |
| /market/level2:BTC-USDT | Incremental order book changes | Market making, spread analysis |
| /market/match:BTC-USDT | Individual trade executions | Volume analysis, footprint charts |
| /market/candles:BTC-USDT_1min | Real-time OHLCV candles | Chart data, pattern detection |
| /account/balance | Account balance changes (private) | Portfolio tracking |
| /spotMarket/tradeOrders | Order status updates (private) | Order lifecycle management |
async def subscribe_channels(ws, symbols):
# Subscribe to tickers for multiple symbols in one message
sub_msg = {
'id': str(uuid.uuid4()),
'type': 'subscribe',
'topic': '/market/ticker:' + ','.join(symbols),
'privateChannel': False,
'response': True
}
await ws.send(json.dumps(sub_msg))
# Also subscribe to Level 2 order book for the first symbol
book_sub = {
'id': str(uuid.uuid4()),
'type': 'subscribe',
'topic': '/market/level2:' + symbols[0],
'privateChannel': False,
'response': True
}
await ws.send(json.dumps(book_sub))
def handle_message(data):
topic = data.get('topic', '')
payload = data.get('data', {})
if 'ticker' in topic:
symbol = topic.split(':')[1]
price = payload.get('price', 'N/A')
best_ask = payload.get('bestAsk', 'N/A')
best_bid = payload.get('bestBid', 'N/A')
print(symbol + ': price=' + price + ' ask=' + best_ask + ' bid=' + best_bid)
elif 'level2' in topic:
changes = payload.get('changes', {})
asks = changes.get('asks', []) # format: [price, size, sequence]
bids = changes.get('bids', [])
print('Order book delta: ' + str(len(asks)) + ' ask levels, ' + str(len(bids)) + ' bid levels')
Account-level data — balance changes, order fills, position updates — requires a private WebSocket channel. This means authenticating the token request with your KuCoin API key, secret, and passphrase. The signing process uses HMAC-SHA256: concatenate the timestamp, HTTP method, and endpoint path, sign with your secret key, and base64-encode the result. KuCoin also requires the API passphrase to be signed separately starting from API key version 2. If you've set up authenticated REST calls on KuCoin before, the same credentials work here.
import hmac
import hashlib
import base64
import time
import requests
def generate_signature(api_secret, timestamp, method, endpoint, body=''):
str_to_sign = timestamp + method + endpoint + body
signature = base64.b64encode(
hmac.new(
api_secret.encode('utf-8'),
str_to_sign.encode('utf-8'),
hashlib.sha256
).digest()
).decode()
return signature
def get_private_ws_token(api_key, api_secret, api_passphrase):
timestamp = str(int(time.time() * 1000))
endpoint = '/api/v1/bullet-private'
sig = generate_signature(api_secret, timestamp, 'POST', endpoint)
passphrase_encrypted = base64.b64encode(
hmac.new(
api_secret.encode('utf-8'),
api_passphrase.encode('utf-8'),
hashlib.sha256
).digest()
).decode()
headers = {
'KC-API-KEY': api_key,
'KC-API-SIGN': sig,
'KC-API-TIMESTAMP': timestamp,
'KC-API-PASSPHRASE': passphrase_encrypted,
'KC-API-KEY-VERSION': '2'
}
res = requests.post(
'https://api.kucoin.com/api/v1/bullet-private',
headers=headers
)
data = res.json()['data']
return data['token'], data['instanceServers'][0]['endpoint']
# Usage
API_KEY = 'your-api-key'
API_SECRET = 'your-api-secret'
API_PASSPHRASE = 'your-passphrase'
token, endpoint = get_private_ws_token(API_KEY, API_SECRET, API_PASSPHRASE)
print('Private token obtained:', token[:20] + '...')
Generate API keys in KuCoin's API Management console. Enable only the permissions you actually need — 'General' for read-only data, 'Trade' for order management. Always restrict the key to your server's IP address. If your key leaks on KuCoin or any exchange including Binance or Gate.io, IP restriction is often the difference between a scary alert and a drained account.
Production trading systems must survive network hiccups, server restarts, and token expiry. A WebSocket that drops and doesn't recover could mean missed signals or worse — an open position that never gets managed. The pattern below combines exponential backoff with automatic token refresh on reconnect. It also cleanly cancels the ping task before each reconnection attempt, avoiding orphaned coroutines that can cause subtle memory leaks in long-running bots. VoiceOfChain uses a similar resilient streaming pattern to maintain continuous data feeds from multiple exchanges simultaneously.
import asyncio
import websockets
import json
import uuid
async def keep_alive(ws, interval):
while True:
await asyncio.sleep(interval)
await ws.send(json.dumps({'id': str(uuid.uuid4()), 'type': 'ping'}))
async def resilient_stream(symbols, on_tick, max_retries=10):
retries = 0
while retries < max_retries:
try:
token, endpoint, ping_ms = get_ws_token() # fresh token each attempt
url = endpoint + '?token=' + token + '&connectId=' + str(uuid.uuid4())
async with websockets.connect(url) as ws:
await ws.recv() # consume welcome message
retries = 0 # reset counter on successful connect
await ws.send(json.dumps({
'id': '1',
'type': 'subscribe',
'topic': '/market/ticker:' + ','.join(symbols),
'privateChannel': False,
'response': True
}))
ping_task = asyncio.create_task(keep_alive(ws, ping_ms // 1000))
try:
async for raw in ws:
msg = json.loads(raw)
if msg.get('type') == 'message':
symbol = msg['topic'].split(':')[1]
on_tick(symbol, msg['data'])
finally:
ping_task.cancel()
except Exception as e:
retries += 1
backoff = min(2 ** retries, 60) # exponential, capped at 60s
print('Connection lost. Retrying in', backoff, 'seconds. Attempt', retries)
await asyncio.sleep(backoff)
raise RuntimeError('Max reconnection attempts exceeded')
def handle_tick(symbol, data):
print(symbol + ': price=' + data.get('price', 'N/A'))
asyncio.run(resilient_stream(['BTC-USDT', 'ETH-USDT', 'SOL-USDT'], handle_tick))
The KuCoin WebSocket API gives you everything needed to build professional-grade market data infrastructure. Start with the public ticker stream to get your connection logic right, then layer in order book subscriptions and private account channels as your system matures. The patterns covered here — token-based connection, ping keepalive, multi-symbol batching, and exponential backoff reconnection — handle the vast majority of production edge cases. For traders who want real-time signals without building infrastructure themselves, platforms like VoiceOfChain aggregate live streaming data from KuCoin, Binance, Bybit, OKX, and other exchanges into actionable trade signals. Whether you build your own pipeline or consume aggregated feeds, the key is having data that is genuinely real-time — not REST-polled approximations with half-second lag.