CryptoCompare WebSocket API: Real-Time Crypto Data Guide
A practical guide to the CryptoCompare WebSocket API — how to connect, subscribe to live price feeds, and use real-time data in Python and JavaScript trading tools.
A practical guide to the CryptoCompare WebSocket API — how to connect, subscribe to live price feeds, and use real-time data in Python and JavaScript trading tools.
Polling a REST endpoint every few seconds is already stale by the time the response arrives. In crypto markets — where a Binance liquidation cascade or a surprise OKX listing can move a token 10% in thirty seconds — latency is a P&L problem, not just a technical footnote. The CryptoCompare WebSocket API solves this by pushing data to you as it happens, maintaining a persistent connection that streams trades, aggregate prices, and order book updates across hundreds of pairs simultaneously. This guide walks through authentication, subscription setup, data parsing, and wiring it all into something that actually trades.
CryptoCompare aggregates market data from over 200 exchanges — including Binance, Bybit, OKX, Coinbase, KuCoin, and Gate.io — and exposes it through a unified WebSocket endpoint at wss://streamer.cryptocompare.com/v2. Unlike REST, you open the connection once and the server continuously pushes updates without you having to ask. There are three channels most traders care about: the AGGREGATE channel (consolidated price across all supported venues), the RAW TRADE channel (individual trades from a specific exchange as they happen), and the ORDER BOOK channel (top-of-book bid/ask updates). The cryptocompare websocket api is the backbone of many algorithmic setups, from simple price alert bots to arbitrage engines that compare Bybit and Binance spot prices tick by tick.
Free API keys have rate limits and may not include all channels. For production bots monitoring more than a handful of pairs, a paid plan is worth it — the free tier is fine for learning and prototyping.
Sign up at the CryptoCompare developer portal, create a project, and generate an API key. Authentication is refreshingly simple — you append the key to the WebSocket URL as a query parameter. No OAuth, no token refresh cycle, no bearer headers. Here is how to establish the initial connection in Python using the websockets library and confirm the handshake:
import asyncio
import websockets
import json
API_KEY = "your_cryptocompare_api_key_here"
WS_URL = f"wss://streamer.cryptocompare.com/v2?api_key={API_KEY}"
async def connect():
try:
async with websockets.connect(WS_URL) as ws:
print("Connected to CryptoCompare WebSocket")
# Subscribe to BTC and ETH aggregate prices
sub_message = {
"action": "SubAdd",
"subs": [
"5~CCCAGG~BTC~USD",
"5~CCCAGG~ETH~USD",
"5~CCCAGG~SOL~USD"
]
}
await ws.send(json.dumps(sub_message))
print("Subscriptions active")
async for message in ws:
data = json.loads(message)
# Skip heartbeats (TYPE 999) and subscription confirmations
if data.get("TYPE") not in ("5", "0", "2"):
continue
print(data)
except websockets.exceptions.ConnectionClosedError as e:
print(f"Connection closed unexpectedly: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
asyncio.run(connect())
The subscription string format is {channel}~{exchange}~{fsym}~{tsym}. Channel 5 is aggregate, channel 0 is raw trades from a named exchange, channel 2 is order book. So '0~Binance~BTC~USDT' streams every individual trade on Binance's BTC/USDT pair in real time, while '5~CCCAGG~BTC~USD' gives you the consolidated price across all CryptoCompare venues. Use the aggregate channel for general monitoring; drop to channel 0 when you need per-exchange granularity for arbitrage or execution decisions.
If you're building a Node.js service or a browser dashboard, the native WebSocket API connects cleanly. The example below subscribes to multiple pairs and handles reconnection automatically — a non-negotiable feature for anything running in production:
const API_KEY = 'your_cryptocompare_api_key_here';
const WS_URL = `wss://streamer.cryptocompare.com/v2?api_key=${API_KEY}`;
const SUBSCRIPTIONS = [
'5~CCCAGG~BTC~USD',
'5~CCCAGG~ETH~USD',
'5~CCCAGG~SOL~USD',
'0~Binance~BTC~USDT' // raw trades from Binance specifically
];
function connect() {
const ws = new WebSocket(WS_URL);
ws.onopen = () => {
console.log('Connected');
ws.send(JSON.stringify({ action: 'SubAdd', subs: SUBSCRIPTIONS }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.TYPE === '5') {
// Aggregate price update
const { FROMSYMBOL, TOSYMBOL, PRICE, FLAGS } = data;
if (PRICE) {
const dir = (FLAGS & 1) ? '▲' : (FLAGS & 2) ? '▼' : '-';
console.log(`${FROMSYMBOL}/${TOSYMBOL}: $${PRICE.toLocaleString()} ${dir}`);
}
} else if (data.TYPE === '0') {
// Raw trade from named exchange
console.log(`Trade @ ${data.M}: ${data.Q} ${data.FSYM} at $${data.P}`);
}
// Ignore TYPE 999 heartbeats silently
};
ws.onerror = (err) => console.error('WS error:', err);
ws.onclose = () => {
console.log('Disconnected. Reconnecting in 5s...');
setTimeout(connect, 5000);
};
}
connect();
Always implement reconnection logic. WebSocket connections drop — network hiccups, server restarts, idle timeouts. A bot that doesn't reconnect is a bot that silently stops working, often at the worst possible moment.
The aggregate channel messages use compact single-letter field names to minimize bandwidth. PRICE is the current price, VOLUME24HOUR is the rolling 24-hour volume, CHANGE24HOUR is the price delta from yesterday, and FLAGS is a bitmask indicating tick direction (1 = up, 2 = down, 4 = unchanged). Critically, CryptoCompare only sends fields that changed since the last message — not the full price object every time. This means you must maintain a local state and merge incoming updates into it. Treating each message as a complete snapshot is one of the most common bugs in new CryptoCompare integrations.
import asyncio
import websockets
import json
from collections import defaultdict
API_KEY = "your_cryptocompare_api_key_here"
WS_URL = f"wss://streamer.cryptocompare.com/v2?api_key={API_KEY}"
TRACKED_FIELDS = {"PRICE", "VOLUME24HOUR", "CHANGE24HOUR", "FLAGS", "LASTUPDATE"}
class PriceFeed:
def __init__(self):
self.state = defaultdict(dict)
def update(self, data: dict) -> dict | None:
if data.get("TYPE") != "5":
return None
fsym = data.get("FROMSYMBOL", "")
tsym = data.get("TOSYMBOL", "")
if not fsym or not tsym:
return None
key = f"{fsym}/{tsym}"
# Merge only fields that arrived — partial updates are normal
self.state[key].update({k: v for k, v in data.items() if k in TRACKED_FIELDS})
return self.state[key]
def is_up(self, pair: str) -> bool:
return bool(self.state.get(pair, {}).get("FLAGS", 0) & 1)
async def stream(feed: PriceFeed):
subs = [
"5~CCCAGG~BTC~USD",
"5~CCCAGG~ETH~USD",
"5~CCCAGG~SOL~USD",
"5~CCCAGG~BNB~USD"
]
while True:
try:
async with websockets.connect(WS_URL) as ws:
await ws.send(json.dumps({"action": "SubAdd", "subs": subs}))
async for raw in ws:
try:
data = json.loads(raw)
pair_state = feed.update(data)
if pair_state and "PRICE" in pair_state:
fsym = data.get("FROMSYMBOL")
tsym = data.get("TOSYMBOL")
pair = f"{fsym}/{tsym}"
arrow = "▲" if feed.is_up(pair) else "▼"
print(f"{pair}: ${pair_state['PRICE']:,.4f} {arrow}")
except (json.JSONDecodeError, KeyError, TypeError) as e:
# Malformed or unexpected message — log and continue
print(f"Parse warning: {e}")
continue
except websockets.exceptions.ConnectionClosedError:
print("Connection lost. Retrying in 3s...")
await asyncio.sleep(3)
if __name__ == "__main__":
asyncio.run(stream(PriceFeed()))
Raw price data becomes useful when it drives decisions. The most common patterns built on top of the cryptocompare websocket api are price alert systems, threshold-based signal triggers, and arbitrage spread monitors. Platforms like VoiceOfChain use real-time data streams exactly this way — ingesting continuous price feeds and surfacing actionable signals to traders the moment conditions are met, without the latency of polling-based approaches.
For a concrete example: monitoring whether BTC's price on Binance diverges more than 0.15% from OKX. Subscribe to both raw trade channels simultaneously — '0~Binance~BTC~USDT' and '0~OKX~BTC~USDT' — maintain separate price states for each exchange, and compute the spread on every incoming update. When the spread exceeds the threshold, fire an alert or trigger an execution. This runs comfortably in a single async Python process and scales to dozens of pairs with minimal overhead.
A few things that bite people in production: CryptoCompare limits concurrent subscriptions based on API tier, so be deliberate about which pairs you need. The aggregate channel already consolidates Bybit, Gate.io, KuCoin, OKX, Binance, and others — for most price monitoring use cases, channel 5 is sufficient. Only use channel 0 when you specifically need per-exchange granularity. Also, TYPE 999 heartbeat messages arrive periodically to keep the connection alive — your parser should ignore them rather than trying to process them as price data.
| Channel | Type Code | Description | Example Subscription |
|---|---|---|---|
| Aggregate Index | 5 | Consolidated price across all exchanges | 5~CCCAGG~BTC~USD |
| Raw Trade | 0 | Individual trades from a specific exchange | 0~Binance~BTC~USDT |
| Order Book L2 | 2 | Top-of-book bid/ask updates | 2~Binance~BTC~USDT |
| Heartbeat | 999 | Server keep-alive ping — ignore in parser | Received automatically |
The CryptoCompare WebSocket API is one of the cleanest ways to get a broad, reliable real-time price feed without maintaining individual connections to Binance, Bybit, OKX, and a dozen other exchanges separately. The subscription model is straightforward, the aggregate channel covers the vast majority of monitoring use cases, and the Python and JavaScript patterns in this guide give you a working foundation to build on. Start with the connection template, get data flowing, then layer your logic on top — whether that is alerting, signal generation, or feeding a platform like VoiceOfChain that surfaces real-time trading signals from live market data. The infrastructure is the easy part; what you do with the stream is where the edge lives.