MeowQuant is an independent third-party information site, not OKX official. This site contains affiliate links, and we may earn a fee from them. Full disclosure →

OKX · API Advanced

OKX WebSocket with ccxt.pro: Tickers, Orders, Demo URLs and Connection Limits

With REST you send a request and the exchange answers once; with WebSocket you open one long-lived connection, subscribe, and the exchange pushes updates to you. OKX's docs, in the WebSocket overview, strongly recommend that developers use the WebSocket API to get market data and order book depth.

The two differ in four ways. Data from REST polling (fetch_) can be up to one polling interval behind, while a WebSocket push (watch_) arrives when the server has an update. Every REST poll is a request and counts toward the endpoint's rate limit; with WebSocket you subscribe once and send no further requests to fetch data. For your own orders, REST means querying again and again to learn whether one filled, while the orders channel pushes on each status change. The cost is in the code: REST is synchronous and easy to debug, and WebSocket is async, with keep-alive and reconnects to handle.

A strategy that checks every few minutes or hours is fine on REST and avoids the async and reconnect layer. WebSocket becomes worth it when you are watching many pairs and polling starts hitting rate limits, or when you need to place the next order right after one fills.

OKX's three WebSocket URLs and common channels

OKX API v5 has three WebSocket URLs, with one set for live and one for demo (live, demo):

URLLiveDemo
Publicwss://ws.okx.com/ws/v5/publicwss://wspap.okx.com/ws/v5/public
Privatewss://ws.okx.com/ws/v5/privatewss://wspap.okx.com/ws/v5/private
Businesswss://ws.okx.com/ws/v5/businesswss://wspap.okx.com/ws/v5/business

The rule in the docs is to use the public service address for public channels and the private service address for private channels. The channels used most:

  • tickers, books (order book depth), trades, candles: public market data, no login needed.
  • orders, positions, account: data about your own account. You have to log in before subscribing, and the login uses the apiKey, the Passphrase and a signature computed with the SecretKey.

The docs state which URL each channel uses, so go by that when you connect directly. With ccxt.pro the library picks: in its okx implementation, subscriptions whose channel name contains candle go to the business URL, other public market data goes to public, and private channels go to private.

Subscribing to tickers with ccxt.pro (watch_ticker)

ccxt's WebSocket side is called ccxt.pro and is merged into the main ccxt package. Regular ccxt goes over REST with the fetch_ family; ccxt.pro goes over WebSocket with the watch_ family. Because it's a long-lived connection underneath, the code has to be written async.

The block below subscribes to BTC/USDT market data and prints on every price update:

import asyncio
import ccxt.pro as ccxtpro     # WS version, the watch_ family

async def main():
    okx = ccxtpro.okx()        # public market data needs no auth
    symbol = 'BTC/USDT'
    try:
        while True:
            ticker = await okx.watch_ticker(symbol)   # returns only on new data
            print(ticker['symbol'], ticker['last'])
    finally:
        await okx.close()       # close the long-lived connection on exit

asyncio.run(main())

The difference from REST is in await okx.watch_ticker(...): it waits for a push and returns only on new market data. Put it in a while True loop and it becomes a continuous stream. The await okx.close() at the end closes the long-lived connection explicitly.

Subscribing to your own orders (watch_orders): demo first

watch_orders returns when the status of one of your orders changes (partial fill, full fill, cancel). It is a private channel, so it logs in with credentials. The example below connects to demo trading: the credentials are the demo Key, followed by set_sandbox_mode(True). A demo Key has to be created separately inside the demo environment; the steps are in How to Use OKX Demo Trading.

import asyncio
import ccxt.pro as ccxtpro

async def main():
    okx = ccxtpro.okx({
        'apiKey':   'YOUR_DEMO_apiKey',
        'secret':   'YOUR_DEMO_secret',
        'password': 'YOUR_DEMO_Passphrase',   # private channels need a login
    })
    okx.set_sandbox_mode(True)                # switch to demo trading

    # Self-check: the URL should contain wspap and the second line should print 1; stop if either is off
    print(okx.urls['api']['ws'])
    print(okx.headers.get('x-simulated-trading'))

    try:
        while True:
            orders = await okx.watch_orders()    # returns only when an order's status changes
            for o in orders:
                print(o['symbol'], o['side'], o['status'], o.get('filled'))
    finally:
        await okx.close()

asyncio.run(main())

With it running, place an order by hand in the demo trading interface and cancel it, and the terminal prints that order's status changes. Here set_sandbox_mode(True) switches the API URLs to the test set, which puts the WebSocket on the demo host wspap.okx.com, and adds x-simulated-trading: 1 to the request headers. The two self-check lines verify the address and the header.

Once the logic checks out and you move to live, remove set_sandbox_mode(True) and swap in the live Key. A live Key should have only Read and Trade ticked, Withdraw left off, and an IP whitelist bound; see API key least privilege.

Connection limits, keep-alive and reconnecting

The Connect and Connection count limit sections of OKX's docs give these figures, and reconnect logic has to stay inside them:

  • New connections: 3 per second, counted per IP.
  • Subscription requests: on each connection, subscribe, unsubscribe and login requests together are limited to 480 per hour.
  • The 30-second rule: if no subscription is made within 30 seconds of connecting, or the server has pushed no data to you for 30 seconds after subscribing, the system closes the connection.
  • Connections per channel: for private channels such as orders, account and positions, at most 30 connections per channel, counted per sub-account.

Keep-alive is a text message with the content ping: when no new message has arrived for N seconds (N below 30), send ping and expect pong back. ccxt.pro's okx implementation does this for you; if you connect directly with a library such as websockets, you have to write it yourself.

A dropped connection has to reconnect automatically, and after reconnecting you must re-subscribe and re-query the current state (pull open orders and positions again), because pushes sent during the drop are lost. The pattern is to wrap the watch_ call in try/except, catch network errors only, wait longer after each failure, and give up once consecutive failures pass a cap. The example below connects to demo trading:

import asyncio
import ccxt.pro as ccxtpro

MAX_FAILS = 5        # give up after more than 5 consecutive failures

async def resync(okx, symbol):
    # pushes sent during the drop are lost, so pull open orders and positions over REST
    open_orders = await okx.fetch_open_orders(symbol)
    positions = await okx.fetch_positions()
    print('Resync:', len(open_orders), 'open orders,', len(positions), 'positions')

async def main():
    okx = ccxtpro.okx({
        'apiKey':   'demo_apiKey',
        'secret':   'demo_secret',
        'password': 'demo_Passphrase',
    })
    okx.set_sandbox_mode(True)
    symbol = 'BTC/USDT'
    fails = 0
    try:
        while True:
            try:
                # after an error, the next call lets ccxt.pro reconnect and re-subscribe
                orders = await okx.watch_orders(symbol)
                fails = 0                             # a push arrived, reset the counter
                for o in orders:
                    print(o['symbol'], o['side'], o['status'])
            except ccxtpro.NetworkError as e:
                fails += 1
                if fails > MAX_FAILS:
                    print('Too many consecutive failures, exiting:', e)
                    break
                wait = min(2 ** fails, 30)            # 2, 4, 8, 16, 30 seconds
                print('Connection issue, retrying in', wait, 'seconds:', e)
                await asyncio.sleep(wait)
                try:
                    await resync(okx, symbol)         # re-query state before subscribing again
                except ccxtpro.NetworkError as e2:
                    print('Resync failed, will try again next round:', e2)
    finally:
        await okx.close()

asyncio.run(main())

This code catches NetworkError only. Authentication failures, bad parameters and the like are not caught; they propagate and stop the script, since retrying does not help with them.

After a drop, don't assume the state hasn't changed; re-query before continuing. While you were disconnected an order may have filled and the price may have moved well away. This is the same step as “check state first, then act after reconnecting” in OKX API risk control.

Risk note: crypto asset prices swing violently, and your capital can shrink sharply or even go to zero. An automated script that goes wrong keeps executing the wrong logic, which amplifies losses; futures and leveraged trading can lead to 100% loss of capital. This article explains how the interface is used, is not investment advice, and you should use only funds you can afford to lose entirely.

FAQ

How many WebSocket connections can subscribe to the OKX orders channel at once?

According to OKX's API documentation, the maximum number of WebSocket connections per channel is 30, counted per sub-account. The channels under this limit are orders, account, positions, balance and positions, position risk warning, and account greeks, and each channel is counted separately. When the limit is exceeded, the most recently subscribed connection first receives the usual subscription success message and then a channel-conn-count-error message, meaning the platform has ended its subscription.

Which URL does ccxt.pro use for OKX candles?

You don't choose it yourself. In ccxt.pro's okx implementation, subscriptions whose channel name contains candle go to the business URL, as does the orders channel for trigger orders; other public market data goes to public, and private channels go to private. After set_sandbox_mode(True), all three paths move to the demo host wspap. If you connect directly, go by the URL the docs list for that channel.

Is ccxt.pro the same thing as regular ccxt?

ccxt.pro is ccxt's WebSocket extension, wrapping each exchange's WS into a unified interface too. Regular ccxt goes over REST (the fetch_ family); ccxt.pro goes over WS (the watch_ family, like watch_ticker and watch_orders). ccxt.pro is now merged into the main ccxt package, so once ccxt is installed you just use the async watch_ methods. Underneath it's a long-lived connection, so the code has to be written as an async loop.