๐Ÿ”Œ API ๐ŸŸก Intermediate

OKX WebSocket API Documentation: A Trader's Complete Guide

Master the OKX WebSocket API with practical code examples, authentication setup, and real-time data streaming techniques every crypto trader needs to know.

Table of Contents
  1. Understanding OKX WebSocket Endpoints and Connection Types
  2. Connecting and Subscribing to Public Market Data
  3. Authentication for Private WebSocket Channels
  4. Processing Order Book Data and Building a Local Book
  5. Error Handling and Reconnection Strategies
  6. Performance Tips and Production Best Practices
  7. Frequently Asked Questions
  8. Conclusion

Real-time market data is the lifeblood of modern crypto trading. REST APIs are fine for placing orders and checking balances, but when you need tick-by-tick price updates, order book changes, and trade executions delivered the instant they happen โ€” WebSockets are the only serious option. OKX, one of the largest derivatives exchanges globally, offers a robust WebSocket API that gives traders direct access to streaming market data, account updates, and order management channels. Understanding the OKX WebSocket API documentation is essential for anyone building automated trading systems or custom dashboards on this exchange.

This guide breaks down everything you need to connect, authenticate, subscribe to channels, parse responses, and handle errors โ€” with working Python code you can run today. Whether you're building a scalping bot, a portfolio tracker, or feeding data into a signal engine like VoiceOfChain, these fundamentals apply to every use case.

Understanding OKX WebSocket Endpoints and Connection Types

OKX provides three distinct WebSocket endpoints, each serving a different purpose. Understanding which one to use is the first step before writing any code. The public endpoint handles market data โ€” tickers, order books, trades, candlesticks โ€” and requires no authentication. The private endpoint delivers account-level data like order updates, position changes, and balance notifications, and requires authentication via API keys. There's also a business endpoint for more advanced features like algo order updates and grid trading notifications.

OKX WebSocket Endpoints
Endpoint TypeURLAuthenticationUse Case
Publicwss://ws.okx.com:8443/ws/v5/publicNot requiredMarket data, tickers, order books
Privatewss://ws.okx.com:8443/ws/v5/privateRequiredOrders, positions, account updates
Businesswss://ws.okx.com:8443/ws/v5/businessRequiredAlgo orders, grid trading, advanced features
Public (AWS)wss://wsaws.okx.com:8443/ws/v5/publicNot requiredMarket data (AWS-routed, lower latency for US/EU)
If you're accessing OKX from the US or Europe, the AWS-routed endpoints (wsaws.okx.com) often provide noticeably lower latency. Always test both and measure round-trip times from your server location.

Each WebSocket connection supports up to 480 subscriptions and will be disconnected if no traffic is detected for 30 seconds. OKX expects you to respond to ping frames, or send your own ping message (the string 'ping') to keep the connection alive. Failing to implement a heartbeat mechanism is the number one reason beginners see their connections silently drop.

Connecting and Subscribing to Public Market Data

Let's start with the simplest and most common use case: subscribing to real-time ticker data for a trading pair. The public WebSocket requires no API keys, making it the perfect starting point. The subscription model uses a simple JSON message where you specify an operation ('subscribe') and an array of channel arguments.

python
import asyncio
import json
import websockets

OKX_WS_PUBLIC = "wss://ws.okx.com:8443/ws/v5/public"

async def subscribe_ticker(symbol: str = "BTC-USDT"):
    """Subscribe to real-time ticker updates for a given instrument."""
    async with websockets.connect(OKX_WS_PUBLIC) as ws:
        # Subscribe to the tickers channel
        subscribe_msg = {
            "op": "subscribe",
            "args": [
                {
                    "channel": "tickers",
                    "instId": symbol
                }
            ]
        }
        await ws.send(json.dumps(subscribe_msg))
        print(f"Subscribed to {symbol} ticker")

        # Keep-alive and message loop
        while True:
            try:
                response = await asyncio.wait_for(ws.recv(), timeout=25)
                
                # Handle ping/pong keepalive
                if response == "pong":
                    continue
                
                data = json.loads(response)
                
                # Check for subscription confirmation
                if "event" in data:
                    print(f"Event: {data['event']} โ€” {data.get('arg', {})}")
                    continue
                
                # Process ticker data
                if "data" in data:
                    for tick in data["data"]:
                        last_price = tick["last"]
                        volume_24h = tick["vol24h"]
                        bid = tick["bidPx"]
                        ask = tick["askPx"]
                        print(f"{symbol} | Last: {last_price} | Bid: {bid} | Ask: {ask} | Vol24h: {volume_24h}")

            except asyncio.TimeoutError:
                # Send ping to keep connection alive
                await ws.send("ping")

asyncio.run(subscribe_ticker("BTC-USDT"))

A few things worth noting in this example. The timeout in wait_for is set to 25 seconds โ€” slightly under OKX's 30-second disconnect threshold. When the timeout fires, we send a 'ping' string (not a JSON message, just the literal string). OKX responds with 'pong'. This keepalive pattern is critical and should be in every WebSocket client you build for this exchange.

You can subscribe to multiple channels in a single message by adding more objects to the args array. Common public channels include 'books' (order book snapshots and incremental updates), 'trades' (individual trade executions), 'candle1m' through 'candle1M' (candlestick data at various timeframes), and 'mark-price' for mark price updates on derivatives.

Authentication for Private WebSocket Channels

Accessing your account data โ€” open orders, fills, position changes, balance updates โ€” requires authenticating the WebSocket connection. OKX uses HMAC-SHA256 signatures, similar to their REST API but with a slightly different signing process. You sign a timestamp and the string 'GET/users/self/verify' with your API secret, then send the credentials as a login message.

python
import asyncio
import json
import hmac
import hashlib
import base64
import time
import websockets

OKX_WS_PRIVATE = "wss://ws.okx.com:8443/ws/v5/private"

# Your OKX API credentials
API_KEY = "your-api-key"
API_SECRET = "your-api-secret"
PASSPHRASE = "your-passphrase"

def generate_signature(timestamp: str, secret: str) -> str:
    """Generate HMAC-SHA256 signature for OKX WebSocket auth."""
    message = f"{timestamp}GET/users/self/verify"
    mac = hmac.new(
        secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256
    )
    return base64.b64encode(mac.digest()).decode("utf-8")

async def private_stream():
    """Connect, authenticate, and stream order updates."""
    async with websockets.connect(OKX_WS_PRIVATE) as ws:
        # Step 1: Authenticate
        timestamp = str(int(time.time()))
        signature = generate_signature(timestamp, API_SECRET)

        login_msg = {
            "op": "login",
            "args": [
                {
                    "apiKey": API_KEY,
                    "passphrase": PASSPHRASE,
                    "timestamp": timestamp,
                    "sign": signature
                }
            ]
        }
        await ws.send(json.dumps(login_msg))
        login_response = await ws.recv()
        result = json.loads(login_response)

        if result.get("event") != "login" or result.get("code") != "0":
            print(f"Authentication failed: {result}")
            return

        print("Authenticated successfully")

        # Step 2: Subscribe to order and position channels
        subscribe_msg = {
            "op": "subscribe",
            "args": [
                {"channel": "orders", "instType": "SPOT"},
                {"channel": "orders", "instType": "SWAP"},
                {"channel": "positions", "instType": "SWAP"},
                {"channel": "account"}
            ]
        }
        await ws.send(json.dumps(subscribe_msg))

        # Step 3: Process incoming messages
        while True:
            try:
                response = await asyncio.wait_for(ws.recv(), timeout=25)
                if response == "pong":
                    continue

                data = json.loads(response)
                channel = data.get("arg", {}).get("channel", "")

                if channel == "orders" and "data" in data:
                    for order in data["data"]:
                        side = order["side"]
                        state = order["state"]
                        inst = order["instId"]
                        px = order.get("fillPx", order.get("px", "N/A"))
                        print(f"ORDER | {inst} {side.upper()} | State: {state} | Price: {px}")

                elif channel == "positions" and "data" in data:
                    for pos in data["data"]:
                        inst = pos["instId"]
                        pnl = pos.get("upl", "0")
                        size = pos.get("pos", "0")
                        print(f"POSITION | {inst} | Size: {size} | Unrealized PnL: {pnl}")

            except asyncio.TimeoutError:
                await ws.send("ping")

asyncio.run(private_stream())
Never hardcode API credentials in your source code. Use environment variables or a secrets manager. If your API key leaks, anyone can read your account data โ€” and if trading permissions are enabled, execute trades on your behalf.

The authentication must happen before any subscription to private channels. If you try to subscribe to 'orders' or 'positions' without logging in first, OKX will return an error and may disconnect you. Also note that the timestamp used for signing must be within 30 seconds of the server time โ€” if your system clock is off, authentication will fail silently.

Processing Order Book Data and Building a Local Book

One of the most valuable WebSocket channels for active traders is the order book feed. OKX provides two book channels: 'books' delivers a full snapshot followed by incremental updates, while 'books5' provides the top 5 levels and is simpler to work with. For production trading systems, you'll want the full 'books' channel, which requires maintaining a local copy of the order book and applying incremental diffs.

The initial snapshot includes a checksum field that you should validate against your local book state. OKX computes the checksum over the top 25 bid and ask levels. If your local checksum diverges from the exchange's, it means you've missed an update and need to resubscribe to get a fresh snapshot. This validation step is essential โ€” trading on a stale or corrupt order book is a fast way to lose money.

python
import asyncio
import json
import zlib
import websockets

class OrderBook:
    """Local order book that processes OKX WebSocket book updates."""

    def __init__(self):
        self.bids = {}  # price -> size
        self.asks = {}  # price -> size

    def process_snapshot(self, data: dict):
        """Initialize book from a full snapshot."""
        self.bids = {entry[0]: entry[1] for entry in data["bids"]}
        self.asks = {entry[0]: entry[1] for entry in data["asks"]}

    def process_update(self, data: dict):
        """Apply incremental update to local book."""
        for bid in data.get("bids", []):
            price, size = bid[0], bid[1]
            if float(size) == 0:
                self.bids.pop(price, None)
            else:
                self.bids[price] = size

        for ask in data.get("asks", []):
            price, size = ask[0], ask[1]
            if float(size) == 0:
                self.asks.pop(price, None)
            else:
                self.asks[price] = size

    def verify_checksum(self, expected_checksum: int) -> bool:
        """Verify local book against OKX checksum."""
        sorted_bids = sorted(self.bids.items(), key=lambda x: -float(x[0]))[:25]
        sorted_asks = sorted(self.asks.items(), key=lambda x: float(x[0]))[:25]

        parts = []
        for i in range(25):
            if i < len(sorted_bids):
                parts.append(f"{sorted_bids[i][0]}:{sorted_bids[i][1]}")
            if i < len(sorted_asks):
                parts.append(f"{sorted_asks[i][0]}:{sorted_asks[i][1]}")

        check_string = ":".join(parts)
        local_checksum = zlib.crc32(check_string.encode("utf-8"))
        # Convert to signed 32-bit integer to match OKX
        if local_checksum >= 2**31:
            local_checksum -= 2**32
        return local_checksum == expected_checksum

    def best_bid_ask(self) -> tuple:
        """Return best bid and ask prices."""
        best_bid = max(self.bids.keys(), key=float) if self.bids else None
        best_ask = min(self.asks.keys(), key=float) if self.asks else None
        return best_bid, best_ask


async def stream_orderbook(symbol: str = "BTC-USDT"):
    url = "wss://ws.okx.com:8443/ws/v5/public"
    book = OrderBook()

    async with websockets.connect(url) as ws:
        sub = {
            "op": "subscribe",
            "args": [{"channel": "books", "instId": symbol}]
        }
        await ws.send(json.dumps(sub))

        while True:
            try:
                msg = await asyncio.wait_for(ws.recv(), timeout=25)
                if msg == "pong":
                    continue

                data = json.loads(msg)
                if "data" not in data:
                    continue

                action = data.get("action", "")
                book_data = data["data"][0]

                if action == "snapshot":
                    book.process_snapshot(book_data)
                    print("Order book snapshot received")
                elif action == "update":
                    book.process_update(book_data)

                    # Verify checksum periodically
                    if "checksum" in book_data:
                        if not book.verify_checksum(book_data["checksum"]):
                            print("Checksum mismatch โ€” resubscribing...")
                            await ws.send(json.dumps({"op": "unsubscribe", "args": [{"channel": "books", "instId": symbol}]}))
                            await ws.send(json.dumps(sub))
                            continue

                bid, ask = book.best_bid_ask()
                spread = f"{float(ask) - float(bid):.2f}" if bid and ask else "N/A"
                print(f"Best Bid: {bid} | Best Ask: {ask} | Spread: {spread}")

            except asyncio.TimeoutError:
                await ws.send("ping")

asyncio.run(stream_orderbook())

This order book implementation is production-grade in terms of correctness but simplified for clarity. In a real trading system, you'd add reconnection logic, logging, and probably run the book updates in a separate asyncio task so your trading logic doesn't block on message processing. Platforms like VoiceOfChain process order book data from multiple exchanges simultaneously to generate actionable trading signals โ€” the underlying WebSocket plumbing looks very similar to what's shown here.

Error Handling and Reconnection Strategies

WebSocket connections drop. It's not a question of if, but when. Network blips, exchange maintenance windows, rate limit violations, and server-side disconnects are all part of the landscape. Any production system built on the OKX WebSocket API must handle these gracefully.

OKX returns error codes in a consistent format. The 'code' field will be '0' for success, and any non-zero value indicates an error. Common error codes include 60001 (invalid request format), 60005 (login required for private channels), 60009 (too many login attempts), and 60011 (invalid API key). When you receive an error, log it, back off, and retry with exponential delays.

  • Implement exponential backoff starting at 1 second, doubling up to a cap of 60 seconds between reconnection attempts
  • Re-authenticate immediately after reconnecting to the private endpoint โ€” sessions don't survive disconnects
  • Resubscribe to all channels after reconnection โ€” subscriptions are tied to the connection, not your account
  • Track and log the reason for each disconnect (clean close, error code, network timeout) to identify recurring issues
  • Set a maximum reconnection count before alerting โ€” if you've reconnected 50 times in an hour, something systemic is wrong
  • Use the OKX status page or their Telegram channel to check for scheduled maintenance before debugging phantom disconnects

Rate limits are another common source of disconnects that the OKX WebSocket API documentation highlights. You're limited to 3 login attempts per connection, and if you exceed the subscription limit (480 per connection), the entire connection drops. For systems monitoring many instruments, distribute subscriptions across multiple WebSocket connections.

Performance Tips and Production Best Practices

Getting a basic connection working is step one. Running it reliably in production with low latency is step two, and it's where most of the engineering effort goes. Here are the patterns that experienced teams use when building on the OKX WebSocket API.

  • Co-locate your server with OKX infrastructure โ€” AWS Tokyo (ap-northeast-1) for the standard endpoint, or use the AWS-routed endpoints if you're in US/EU regions
  • Use message queues (Redis Streams, Kafka) between your WebSocket receiver and your trading logic to decouple ingestion from processing
  • Parse JSON with orjson or ujson instead of the standard library json module โ€” the difference is significant at thousands of messages per second
  • Avoid processing messages synchronously in the receive loop โ€” offload heavy computation to worker tasks or threads
  • Monitor WebSocket latency by comparing OKX's server timestamp (included in most messages) against your local clock
  • For order book channels, the 'books50-l2-tbt' channel provides tick-by-tick updates for the top 50 levels โ€” ideal for HFT strategies that need maximum granularity
OKX provides simulated trading endpoints (wss://wspap.okx.com:8443/ws/v5/...) that mirror production behavior. Always test new WebSocket code against the simulated environment before connecting to live markets.

For multi-instrument strategies, consider whether you truly need real-time WebSocket data for every pair. A common architecture is to use WebSockets for your active trading pairs and fall back to REST polling at longer intervals for watchlist instruments. This reduces connection count and keeps your system focused on the data that actually drives trading decisions.

Frequently Asked Questions

How many WebSocket connections can I have with OKX simultaneously?

OKX allows up to 3 private WebSocket connections and multiple public connections per API key. Each connection supports up to 480 subscriptions. If you need more channels, open additional connections and distribute your subscriptions across them.

Why does my OKX WebSocket connection keep dropping after 30 seconds?

OKX disconnects idle connections after 30 seconds of no activity. You must implement a keepalive mechanism by sending the string 'ping' before the timeout. Set a timer at 25 seconds and send a ping if no other traffic has occurred.

Can I place orders through the OKX WebSocket API?

Yes, OKX supports order placement, modification, and cancellation via WebSocket using the private endpoint. Send a message with 'op' set to 'order', 'amend-order', or 'cancel-order'. This is faster than REST for time-sensitive executions since the connection is already established.

What is the difference between 'books' and 'books5' channels?

The 'books5' channel sends the top 5 bid and ask levels as complete snapshots on every update โ€” simple but higher bandwidth. The 'books' channel sends a full initial snapshot followed by incremental diffs, which is more efficient for deep book tracking but requires you to maintain local state and verify checksums.

Do I need separate API keys for WebSocket and REST API access?

No, the same API key works for both WebSocket and REST endpoints. However, rate limits are tracked separately. WebSocket authentication uses a slightly different signing method โ€” you sign the string 'GET/users/self/verify' with a timestamp rather than signing individual request parameters.

How do I test my OKX WebSocket code without risking real funds?

OKX provides a simulated trading environment at wspap.okx.com with the same API structure as production. Create API keys on the OKX demo trading platform and connect to wss://wspap.okx.com:8443/ws/v5/public or /private. The data and behavior closely mirror live markets.

Conclusion

The OKX WebSocket API is a powerful tool for any trader serious about real-time market data and low-latency execution. The public endpoint gets you streaming prices and order books with zero authentication overhead. The private endpoint, once authenticated, delivers instant order and position updates that REST polling simply can't match. The code examples in this guide give you working building blocks โ€” a public ticker subscriber, an authenticated private stream, and a checksum-verified order book โ€” that you can extend into full trading systems.

The key to success is handling the operational realities: keepalive pings, reconnection logic, checksum validation, and proper error handling. Get these fundamentals right and you have a solid foundation for everything from simple price monitors to sophisticated algorithmic strategies. Combined with signal platforms like VoiceOfChain for trade ideas and timing, a well-built WebSocket integration turns raw market data into a genuine trading edge.