◈   ⌘ api · Intermediate

OKX V5 Trade WebSocket: Real-Time Order Execution Guide

Master OKX V5 Trade WebSocket for live order placement, position tracking, and account updates with Python and JavaScript code examples.

Uncle Solieditor · voc · 06.05.2026 ·views 23
◈   Contents
  1. → V5 WebSocket Architecture: What You're Actually Connecting To
  2. → Authentication: The Login Message
  3. → Placing Orders Over WebSocket
  4. → Subscribing to Order and Position Channels
  5. → Error Handling and Reconnection Logic
  6. → Performance Comparison: OKX vs Other Exchanges
  7. → Frequently Asked Questions
  8. → Conclusion

OKX's V5 WebSocket API is one of the most capable real-time trading interfaces available to retail algo traders today. While platforms like Binance and Bybit get most of the community attention, OKX's V5 private WebSocket channels offer something genuinely compelling: sub-100ms order acknowledgment, unified account streaming, and a clean authentication flow that holds up under production load. If you're building execution infrastructure — whether a simple bot or a full strategy engine — understanding the trade WebSocket is non-negotiable.

V5 WebSocket Architecture: What You're Actually Connecting To

OKX splits its V5 WebSocket into public and private endpoints. The public feed handles market data — order books, trades, tickers. The private endpoint, which is what 'trade WebSocket' refers to, handles everything that requires authentication: placing orders, canceling them, monitoring fills, and streaming account/position updates. The production private endpoint is wss://ws.okx.com:8443/ws/v5/private. There's also a demo environment at wss://wspap.okx.com:8443/ws/v5/private?brokerId=9999 for testing without real funds — use it.

Unlike REST, where each request opens and closes a connection, the WebSocket stays open. You authenticate once, subscribe to channels, and then push orders or receive updates as messages on that same persistent connection. For high-frequency strategies this matters enormously — you're not paying TCP handshake latency on every order. Even for slower systematic strategies, the push-based fill and position updates beat polling REST endpoints every time.

Authentication: The Login Message

OKX V5 uses HMAC-SHA256 authentication over WebSocket. You send a login operation as the very first message after connecting. The signature covers a timestamp, the method string 'GET', and the path '/users/self/verify'. Get this wrong and every subsequent message returns an error 60009. Here's a complete Python authentication setup:

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

API_KEY = 'your_api_key'
API_SECRET = 'your_api_secret'
PASSPHRASE = 'your_passphrase'
WS_URL = 'wss://ws.okx.com:8443/ws/v5/private'

def generate_signature(timestamp: str, secret: str) -> str:
    message = timestamp + 'GET' + '/users/self/verify'
    mac = hmac.new(
        bytes(secret, encoding='utf8'),
        bytes(message, encoding='utf-8'),
        digestmod='sha256'
    )
    return base64.b64encode(mac.digest()).decode()

def build_login_message() -> dict:
    timestamp = str(int(time.time()))
    return {
        'op': 'login',
        'args': [{
            'apiKey': API_KEY,
            'passphrase': PASSPHRASE,
            'timestamp': timestamp,
            'sign': generate_signature(timestamp, API_SECRET)
        }]
    }

async def connect_and_login():
    async with websockets.connect(WS_URL) as ws:
        login_msg = build_login_message()
        await ws.send(json.dumps(login_msg))
        
        response = await ws.recv()
        data = json.loads(response)
        
        if data.get('event') == 'login' and data.get('code') == '0':
            print('Authenticated successfully')
            return ws
        else:
            raise Exception(f'Login failed: {data}')

asyncio.run(connect_and_login())
OKX timestamps must be Unix seconds (not milliseconds). Sending milliseconds is the most common auth failure. Also note: each API key requires the 'Trade' permission enabled in the OKX key settings — read-only keys will authenticate but reject all order operations.

Placing Orders Over WebSocket

Once authenticated, you can place orders by sending an 'order' operation message. This is where the V5 private WebSocket earns its keep — you get order acknowledgment pushed back to you on the same connection, typically within 30-80ms depending on your geographic proximity to OKX's matching engine. Compare that to REST round-trips of 150-300ms from most US-based servers.

OKX supports batch orders in a single WebSocket message (up to 20 orders), which is significant for market-making strategies. Bybit and Binance have similar batch capabilities, but OKX's implementation through V5 is particularly clean for cross-margin unified accounts.

import asyncio
import websockets
import json
import uuid

async def place_order(ws, inst_id: str, side: str, price: str, size: str):
    """
    Place a limit order via OKX V5 Trade WebSocket.
    side: 'buy' or 'sell'
    """
    client_order_id = str(uuid.uuid4()).replace('-', '')[:32]
    
    order_msg = {
        'id': str(uuid.uuid4()),  # request ID for tracking
        'op': 'order',
        'args': [{
            'instId': inst_id,       # e.g. 'BTC-USDT'
            'tdMode': 'cash',        # 'cash' for spot, 'cross' or 'isolated' for margin
            'side': side,
            'ordType': 'limit',
            'px': price,
            'sz': size,
            'clOrdId': client_order_id
        }]
    }
    
    await ws.send(json.dumps(order_msg))
    
    # Wait for acknowledgment
    while True:
        response = await ws.recv()
        data = json.loads(response)
        
        if data.get('op') == 'order':
            for result in data.get('data', []):
                if result.get('clOrdId') == client_order_id:
                    if data.get('code') == '0':
                        print(f"Order placed: {result['ordId']}")
                        return result['ordId']
                    else:
                        raise Exception(f"Order failed: {result.get('sMsg')}")

# Batch order example
async def place_batch_orders(ws, orders: list):
    batch_msg = {
        'id': str(uuid.uuid4()),
        'op': 'batch-orders',
        'args': orders  # list of order dicts, max 20
    }
    await ws.send(json.dumps(batch_msg))
    response = json.loads(await ws.recv())
    return response

Subscribing to Order and Position Channels

Beyond sending orders, the private WebSocket lets you subscribe to push updates for fills, position changes, and account balance. This is critical for any strategy that needs to track its state without polling. The three most important private channels are: orders (fill updates), positions, and account (balance changes). You subscribe to these immediately after login.

const WebSocket = require('ws');
const crypto = require('crypto');

const API_KEY = 'your_api_key';
const API_SECRET = 'your_api_secret';
const PASSPHRASE = 'your_passphrase';

function getSignature(timestamp) {
  const message = `${timestamp}GET/users/self/verify`;
  return crypto
    .createHmac('sha256', API_SECRET)
    .update(message)
    .digest('base64');
}

const ws = new WebSocket('wss://ws.okx.com:8443/ws/v5/private');

ws.on('open', () => {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  
  // Step 1: Login
  ws.send(JSON.stringify({
    op: 'login',
    args: [{ apiKey: API_KEY, passphrase: PASSPHRASE, timestamp, sign: getSignature(timestamp) }]
  }));
});

ws.on('message', (raw) => {
  const msg = JSON.parse(raw);
  
  // Step 2: After login, subscribe
  if (msg.event === 'login' && msg.code === '0') {
    console.log('Logged in, subscribing to channels...');
    ws.send(JSON.stringify({
      op: 'subscribe',
      args: [
        { channel: 'orders', instType: 'SPOT' },
        { channel: 'positions', instType: 'MARGIN' },
        { channel: 'account' }
      ]
    }));
    return;
  }
  
  // Step 3: Handle streaming data
  if (msg.arg?.channel === 'orders') {
    for (const order of msg.data || []) {
      console.log(`Order ${order.ordId} state: ${order.state}, filled: ${order.fillSz}`);
    }
  }
  
  if (msg.arg?.channel === 'positions') {
    for (const pos of msg.data || []) {
      console.log(`Position ${pos.instId}: size=${pos.pos}, uPnL=${pos.upl}`);
    }
  }
});

ws.on('error', (err) => console.error('WebSocket error:', err));
ws.on('close', () => console.log('Connection closed — implement reconnect logic here'));

A few things worth noting in production: OKX sends a ping every 30 seconds and expects a 'pong' text frame back within 30 seconds or it closes the connection. Implement a ping handler. Also, if you're running a strategy that integrates signals from VoiceOfChain — a real-time crypto signal platform — you'll want your WebSocket handler to be non-blocking so incoming signals can immediately trigger order operations without waiting for previous message processing to complete. Use asyncio queues or separate threads for signal ingestion versus order execution.

Error Handling and Reconnection Logic

WebSocket connections drop. OKX's infrastructure is reliable, but network hiccups, server-side maintenance windows, and rate limit violations all cause disconnects. A production-grade implementation needs reconnection logic with exponential backoff, state reconciliation on reconnect (re-fetch open orders via REST before re-subscribing), and handling for specific OKX error codes.

Common OKX V5 WebSocket Error Codes
CodeMeaningAction
60009Login failed — bad signature or timestampCheck HMAC logic, verify timestamp is seconds not ms
60011Already logged inDon't send login twice — track auth state
60012Illegal request — malformed JSONValidate JSON before sending
60018Invalid opCheck operation name — 'order' not 'place-order'
51000Parameter errors in order argsValidate instId, size precision, and tdMode
51008Insufficient balanceCheck account balance before placing
import asyncio
import websockets
import json
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('okx_ws')

class OKXTradeWebSocket:
    def __init__(self, api_key, secret, passphrase):
        self.api_key = api_key
        self.secret = secret
        self.passphrase = passphrase
        self.ws = None
        self.reconnect_delay = 1  # seconds, will backoff
    
    async def connect(self):
        while True:
            try:
                async with websockets.connect(
                    'wss://ws.okx.com:8443/ws/v5/private',
                    ping_interval=20,
                    ping_timeout=30
                ) as ws:
                    self.ws = ws
                    self.reconnect_delay = 1  # reset on success
                    await self._login()
                    await self._subscribe()
                    await self._listen()
            except websockets.exceptions.ConnectionClosed as e:
                logger.warning(f'Connection closed: {e}. Reconnecting in {self.reconnect_delay}s')
                await asyncio.sleep(self.reconnect_delay)
                self.reconnect_delay = min(self.reconnect_delay * 2, 60)  # cap at 60s
            except Exception as e:
                logger.error(f'Unexpected error: {e}')
                await asyncio.sleep(self.reconnect_delay)
    
    async def _listen(self):
        async for raw in self.ws:
            if raw == 'ping':
                await self.ws.send('pong')
                continue
            
            try:
                msg = json.loads(raw)
                await self._handle_message(msg)
            except json.JSONDecodeError:
                logger.error(f'Bad JSON: {raw[:100]}')
    
    async def _handle_message(self, msg):
        event = msg.get('event')
        if event == 'error':
            logger.error(f"WS error {msg.get('code')}: {msg.get('msg')}")
        elif msg.get('arg', {}).get('channel') == 'orders':
            for order in msg.get('data', []):
                logger.info(f"Fill update: {order['instId']} {order['side']} {order['fillSz']} @ {order['fillPx']}")
Always reconcile state on reconnect. When your WebSocket drops and reconnects, query open orders via REST (GET /api/v5/trade/orders-pending) before assuming your previous state is still valid. Fills can arrive during a disconnect window and you won't get the WebSocket push for them on the new connection.

Performance Comparison: OKX vs Other Exchanges

For traders choosing between exchange WebSocket implementations, the differences matter. Binance's WebSocket API is widely documented and has the deepest community support, but its private WebSocket for order execution was a later addition compared to its mature market data streams. Bybit's V5 WebSocket is very similar in design to OKX's and is worth benchmarking head-to-head if you're running latency-sensitive strategies. Gate.io and KuCoin both offer WebSocket trading but with notably less reliability under high market volatility — you'll see more reconnection events during peak periods. Bitget's API is solid for futures but the documentation gaps make it harder to implement correctly on first pass. OKX's V5 API distinguishes itself with unified account support and the demo trading environment being a genuine like-for-like replica of production, which saves significant debugging time.

For signal-driven execution specifically, combining a real-time signal feed from VoiceOfChain with OKX's trade WebSocket gives you a clean architecture: signals arrive via VoiceOfChain's API, your bot processes the signal and fires the order through the persistent WebSocket connection, and fill confirmations come back on the same channel. No polling, no extra REST calls in the hot path.

Frequently Asked Questions

What is the difference between OKX V5 public and private WebSocket?
The public WebSocket streams market data like order books, trades, and tickers — no authentication required. The private WebSocket handles everything account-related: placing orders, canceling them, and receiving real-time fill and position updates. For trading bots, you typically run both simultaneously.
How many orders per second can I send over OKX V5 trade WebSocket?
OKX's rate limit for WebSocket order placement is 60 orders per 2 seconds for spot, and 60 per 2 seconds for futures per instrument. Batch-orders lets you send up to 20 orders in a single message, which is the most efficient way to approach the rate limit ceiling.
Does OKX V5 WebSocket support stop-loss and take-profit orders?
Yes. You can place conditional orders (stop-market, stop-limit) via the WebSocket using ordType values like 'conditional' combined with slTriggerPx and tpTriggerPx parameters. OKX also supports one-cancels-other (OCO) order types through the same interface.
Why does my OKX WebSocket authentication keep failing with error 60009?
The most common cause is using millisecond timestamps instead of second timestamps. OKX expects Unix seconds (e.g. 1714900000, not 1714900000000). Also verify your HMAC message is exactly: timestamp + 'GET' + '/users/self/verify' with no spaces or extra characters.
Can I use OKX V5 trade WebSocket for paper trading before going live?
Yes. OKX provides a dedicated demo environment at wss://wspap.okx.com:8443/ws/v5/private?brokerId=9999 that mirrors production behavior. Create a demo account in OKX settings, generate demo API keys, and your code will work identically — the only difference is the endpoint URL.
How do I handle WebSocket disconnections in a live trading bot?
Implement exponential backoff reconnection (start at 1s, double each attempt, cap at 60s). On each reconnect, re-authenticate, re-subscribe to channels, and reconcile open order state via the REST API before resuming normal operation. Never assume your in-memory order state survived the disconnect.

Conclusion

OKX's V5 trade WebSocket is production-ready infrastructure for algorithmic traders who need real-time order execution without REST polling overhead. The authentication model is straightforward once you nail the timestamp format, the channel subscription system is consistent and predictable, and the demo environment makes testing genuinely useful rather than a liability. Whether you're building a simple signal follower that acts on VoiceOfChain alerts or a multi-leg market-making strategy, the patterns covered here — authentication, order placement, fill streaming, and reconnection handling — form the complete foundation. Start with the demo environment, run your reconnection logic under artificial failures, and only point at production once fills are being correctly accounted for in your position tracking.

◈   more on this topic
◉ basics Mastering the ccxt library documentation for crypto traders ⌂ exchanges Mastering the Binance CCXT Library for Crypto Traders ⌬ bots Best Crypto Trading Bots 2025: Profitable AI-Powered Strategies