CryptoCompare WebSocket API: Real-Time Crypto Data Guide
A practical guide to the CryptoCompare WebSocket API — connect, authenticate, parse live prices, and handle errors with real Python code examples for active traders.
A practical guide to the CryptoCompare WebSocket API — connect, authenticate, parse live prices, and handle errors with real Python code examples for active traders.
If you're watching BTC move 2% in sixty seconds while your app is still polling a REST endpoint, you already understand the problem. REST APIs pull data on a schedule — WebSockets push it the moment it changes. For traders who need tick-by-tick prices from Binance, Bybit, or OKX without building separate integrations for each exchange, the CryptoCompare WebSocket API offers a single unified stream that aggregates data across hundreds of venues. This guide walks through exactly how to connect, authenticate, parse messages, and keep that connection alive under real trading conditions.
The core difference between WebSocket and API (REST specifically) comes down to who initiates the conversation. With a REST API, your application sends a request and waits for a response — every single time. That's fine for account balances or historical candles. It's terrible for live prices. Every poll adds latency, wastes bandwidth on data that hasn't changed, and burns through rate limits fast when you're watching dozens of pairs.
A WebSocket opens a persistent two-way connection between your client and the server. Once the handshake completes, the server pushes data to you the instant something changes — no polling, no waiting, no repeated HTTP overhead. This is what makes a websocket real time example so compelling for trading: you're not asking 'what's the price?' every second, the server is telling you 'price changed' the moment it does. The practical impact on Binance alone: a REST poll might deliver a price 2-5 seconds stale by the time it processes. A WebSocket connection delivers within milliseconds.
Understanding what is websocket api at the protocol level matters too. A WebSocket starts as an ordinary HTTP request that then upgrades via the Upgrade header — which means it works through most firewalls that allow HTTPS traffic. This is why WebSockets are preferred over raw TCP sockets for trading infrastructure: no special network configuration required.
| Feature | REST API | WebSocket API |
|---|---|---|
| Data delivery | Pull — you request it | Push — server sends it |
| Latency | High (full request cycle) | Low (persistent connection) |
| Rate limits | Burns fast with polling | Single connection, event-driven |
| Best for | Historical data, account info | Live prices, order book, trades |
| Complexity | Simple request/response | Moderate (connection management) |
CryptoCompare aggregates data from over 300 exchanges including Binance, Coinbase, OKX, KuCoin, and Gate.io. Their WebSocket API streams several data types through typed subscription channels. Knowing what's available before you write a single line saves significant trial and error.
Subscription strings follow a consistent pattern: TYPE~EXCHANGE~FROMSYMBOL~TOSYMBOL. For the aggregate feed, exchange is always CCCAGG. For exchange-specific data, use the exchange name directly — '0~Binance~BTC~USDT' gives you individual Binance trades as they happen. '2~Coinbase~ETH~USD' gives you Coinbase-specific price updates.
CryptoCompare requires an API key for WebSocket access. The free tier allows limited simultaneous subscriptions and message rates. For production bots tracking more than 10-15 pairs, a paid plan is practically necessary — free tier rate limits will interrupt streams during high-volatility periods when you need data most.
Register at cryptocompare.com and generate an API key from the dashboard. The key is passed as a query parameter on the WebSocket URL — there's no separate handshake or header-based authentication. Before building the WebSocket connection, verify your key is active against the REST endpoint first. This catches invalid keys before you spend time debugging connection issues.
import requests
API_KEY = "your_api_key_here"
# Verify API key works against REST endpoint before connecting WebSocket
url = "https://min-api.cryptocompare.com/data/price"
params = {
"fsym": "BTC",
"tsyms": "USD,EUR",
"api_key": API_KEY
}
response = requests.get(url, params=params)
data = response.json()
if "Response" in data and data["Response"] == "Error":
print(f"API key error: {data['Message']}")
exit(1)
else:
print(f"Key valid. BTC/USD: ${data['USD']:}")
# Output: Key valid. BTC/USD: $45,230
# Install dependencies first:
# pip install websocket-client requests
For synchronous bots, install websocket-client (pip install websocket-client). For async workflows using asyncio — common in more complex trading systems — use the websockets library instead (pip install websockets). The examples below use websocket-client for clarity, but the subscription logic and message format are identical in either case.
Here's a complete working connection that subscribes to BTC, ETH, and SOL aggregate price streams. This is the websocket real time example most traders start with — it connects, subscribes, and prints every price update as it arrives from the CryptoCompare aggregator.
import websocket
import json
API_KEY = "your_api_key_here"
WS_URL = f"wss://streamer.cryptocompare.com/v2?api_key={API_KEY}"
def on_open(ws):
# Subscribe to aggregate price streams for BTC, ETH, and SOL
subscription = {
"action": "SubAdd",
"subs": [
"5~CCCAGG~BTC~USD",
"5~CCCAGG~ETH~USD",
"5~CCCAGG~SOL~USD"
]
}
ws.send(json.dumps(subscription))
print("Subscribed to BTC, ETH, SOL aggregate streams")
def on_message(ws, message):
data = json.loads(message)
msg_type = data.get("TYPE")
# Type 5 = CURRENT_AGGREGATE — the main price update message
if msg_type == "5" and "PRICE" in data:
symbol = data.get("FROMSYMBOL")
price = data.get("PRICE")
change_pct = data.get("CHANGEPCT24HOUR", 0)
direction = "+" if change_pct >= 0 else ""
print(f"{symbol}/USD: ${price:,.2f} ({direction}{change_pct:.2f}% 24h)")
# Type 999 = heartbeat to keep connection alive
elif msg_type == "999":
pass
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} - {close_msg}")
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
ws.run_forever(ping_interval=30, ping_timeout=10)
Run this and you'll see output like: BTC/USD: $45,230.00 (+2.14% 24h). Each message is a partial update — CryptoCompare only sends fields that changed since the last message. That's why checking 'PRICE' in data before accessing it matters: the first few messages after subscribing are setup confirmations that arrive without price data included.
The api gateway websocket example from AWS documentation follows a similar subscribe/publish pattern. The key difference: AWS API Gateway requires route keys like '$connect' and '$disconnect', while CryptoCompare uses a simple JSON action field. The underlying WebSocket protocol and handshake are identical — the conceptual model transfers directly.
Raw price updates are a foundation, not a finish line. Most trading systems need structure: track state across updates, detect momentum, and forward signals somewhere actionable. Platforms like VoiceOfChain consume exactly this kind of real-time feed — processing live WebSocket data across Binance, Bybit, OKX, and Coinbase pairs and turning price movements into structured trading signals.
Here's a parser that maintains local price state and fires a signal when any symbol moves more than 1% from its last known price — a simple but effective momentum detector suitable for alerting or bot triggering.
import websocket
import json
from collections import defaultdict
from datetime import datetime
API_KEY = "your_api_key_here"
WS_URL = f"wss://streamer.cryptocompare.com/v2?api_key={API_KEY}"
price_state = defaultdict(dict)
WATCHLIST = [
"5~CCCAGG~BTC~USD",
"5~CCCAGG~ETH~USD",
"5~CCCAGG~SOL~USD",
"5~CCCAGG~BNB~USD",
"5~CCCAGG~MATIC~USD"
]
def parse_update(data):
symbol = data.get("FROMSYMBOL")
price = data.get("PRICE")
if not symbol or not price:
return
prev = price_state[symbol].get("price")
if prev:
change_pct = ((price - prev) / prev) * 100
if abs(change_pct) >= 1.0:
direction = "UP" if change_pct > 0 else "DOWN"
ts = datetime.utcnow().strftime("%H:%M:%S")
print(f"[{ts}] SIGNAL {symbol} {direction} {abs(change_pct):.2f}% | ${prev:,.2f} -> ${price:,.2f}")
# Forward to your bot, webhook, or alerting system here
price_state[symbol]["price"] = price
price_state[symbol]["volume"] = data.get("VOLUMEHOUR", price_state[symbol].get("volume", 0))
def on_open(ws):
ws.send(json.dumps({"action": "SubAdd", "subs": WATCHLIST}))
print(f"Watching {len(WATCHLIST)} pairs for 1%+ moves")
def on_message(ws, message):
data = json.loads(message)
if data.get("TYPE") == "5":
parse_update(data)
ws = websocket.WebSocketApp(WS_URL, on_open=on_open, on_message=on_message)
ws.run_forever(ping_interval=30, ping_timeout=10)
This pattern — maintain local state, compute delta, emit signal on threshold — is the core loop behind most real-time signal generators. VoiceOfChain extends this with multi-timeframe confirmation, volume weighting, and cross-exchange spread validation before surfacing alerts to traders. The WebSocket layer is the same; the intelligence is layered on top.
WebSocket connections drop — CryptoCompare's servers restart, your ISP hiccups, NAT timeouts kill idle connections. A trading bot that goes dark without recovery is a liability. Proper error handling means automatic reconnection, re-subscription, and resumption — without hammering the server if it's having issues.
import websocket
import json
import time
API_KEY = "your_api_key_here"
WS_URL = f"wss://streamer.cryptocompare.com/v2?api_key={API_KEY}"
SUBSCRIPTIONS = [
"5~CCCAGG~BTC~USD",
"5~CCCAGG~ETH~USD"
]
class CryptoCompareStream:
def __init__(self, subs, max_retries=10):
self.subs = subs
self.max_retries = max_retries
self.retry_count = 0
def on_open(self, ws):
self.retry_count = 0 # reset on successful connect
ws.send(json.dumps({"action": "SubAdd", "subs": self.subs}))
print("Connected and subscribed")
def on_message(self, ws, message):
data = json.loads(message)
if data.get("TYPE") == "5" and "PRICE" in data:
print(f"{data['FROMSYMBOL']}: ${data['PRICE']:,.2f}")
def on_error(self, ws, error):
print(f"Error: {error}")
def on_close(self, ws, code, msg):
print(f"Closed (code={code})")
if self.retry_count < self.max_retries:
# Exponential backoff capped at 60 seconds
wait = min(2 ** self.retry_count, 60)
print(f"Reconnecting in {wait}s (attempt {self.retry_count + 1}/{self.max_retries})")
time.sleep(wait)
self.retry_count += 1
self.start()
else:
print("Max retries reached. Check API key and network.")
def start(self):
ws = websocket.WebSocketApp(
WS_URL,
on_open=self.on_open,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close
)
# ping_interval keeps connection alive through NAT timeouts
ws.run_forever(ping_interval=30, ping_timeout=10)
stream = CryptoCompareStream(SUBSCRIPTIONS)
stream.start()
The exponential backoff (2, 4, 8, 16... seconds, capped at 60) is critical. If CryptoCompare's servers are under load, immediate reconnection attempts will trigger rate limiting and make recovery slower. The ping_interval=30 parameter keeps the TCP connection alive through NAT timeouts — without it, connections silently die after a few minutes of low-activity periods on many networks.
Always re-subscribe after every reconnection. The server has no memory of your previous subscriptions — each new WebSocket handshake starts fresh. If your watchlist is dynamic (you add or remove symbols at runtime), store subscriptions in a shared list that the on_open handler reads, so reconnections always pick up the current state.
The CryptoCompare WebSocket API sits at a useful point in the trading data stack: it handles aggregation complexity across Binance, OKX, Coinbase, Gate.io, and hundreds of others so your code manages one connection instead of dozens. That's a meaningful reduction in infrastructure overhead for anyone building multi-exchange monitoring or signal generation.
The patterns covered here — key verification, subscription management, partial update parsing, and exponential backoff reconnection — are the same patterns used in production trading systems. Start with a single symbol subscription, verify your message handler gracefully skips updates without a PRICE field, then scale to your full watchlist once the plumbing is solid.
For traders who'd rather consume processed signals than manage WebSocket infrastructure, platforms like VoiceOfChain handle the data layer — aggregating real-time feeds across major exchanges and surfacing structured signals directly. Whether you build it yourself or consume a processed feed, the underlying mechanics are identical: persistent connections, event-driven processing, and resilient reconnection logic. That's what separates reliable trading infrastructure from a fragile polling script.