Error Handling & Reconnection
Handshake errors
| HTTP code | Cause | Action |
|---|---|---|
426 | Malformed upgrade (missing/invalid Sec-WebSocket-Key) | Fix the WebSocket client |
401 | Missing X-API-Key header | Add the header |
403 | Invalid, revoked, or expired key | Request a new key |
429 | Rate or connection limit hit | Back off, see Rate Limits |
Close codes
| Code | Reason | Meaning | Reconnect? |
|---|---|---|---|
1000 | key_expired | Key expiration date passed | No — get a new key |
1000 | key_invalidated | Administrator revoked the key | No — get a new key |
1008 | too_slow | Client lagged > 10 messages behind | Yes — process faster |
1008 | rate_limit_exceeded | Sent > 3 messages / minute to the server | Yes — stop sending |
1009 | frame_too_large | Sent a frame larger than 1 KB | Yes — fix the client |
Reconnection strategy
Exponential backoff, capped at 5 minutes:
attempt 1 → 1 s
attempt 2 → 2 s
attempt 3 → 4 s
attempt 4 → 8 s
… cap at 300 s
Reset the counter after a successful connection.
Decision logic:
| Trigger | Action |
|---|---|
Close key_expired / key_invalidated | Stop |
Close rate_limit_exceeded | Wait 60 s, stop sending client messages, reconnect |
Close too_slow | Reconnect immediately, process faster |
HTTP 429 | Back off (you hit a connection limit) |
| Any other disconnect | Reconnect with exponential backoff |
Detecting dead connections
The server sends a WebSocket PING every 15 s and closes the connection if no PONG arrives within 30 s. Your library handles PONG automatically and surfaces the disconnect as a close event.
import asyncio, json, websockets
async def stream(ws):
try:
async for raw in ws:
data = json.loads(raw)
if data["type"] == "announcement":
handle(data)
except websockets.ConnectionClosed as e:
print(f"closed code={e.code} reason={e.reason!r}")
Don’t wrap recv() in a timeout shorter than ~60 s as a liveness check. Listings are sparse — you’ll time out and reconnect during quiet periods. Trust the library’s PING/PONG, or watch for the 30 s heartbeat.