WebSocket

Channels, subscribe frames, socket authentication, sequence numbers and connection hygiene.

One socket carries market data and private account updates:

wss://api-mainnet.1024ex.com/api/v1/ws
wss://api-testnet-stable.1024ex.com/api/v1/ws

Public channels need no authentication. Private channels require authenticating the socket itself.

Frames

Requests are JSON objects with method and params — not op:

{ "id": "1", "method": "subscribe", "params": { "channel": "perp.ticker", "market": "BTC-USDC" } }

Methods: subscribe, unsubscribe, auth, ping. id is echoed back so you can correlate replies.

An acknowledgement, then data:

{ "id": "1", "type": "subscribed", "channel": "perp.ticker:BTC-USDC", "success": true, "timestamp": 1790094770315 }
{
  "channel": "perp.ticker:BTC-USDC",
  "market": "BTC-USDC",
  "event": "snapshot",
  "seq": 1,
  "data": {
    "market": "BTC-USDC", "bid": "86115.74", "ask": "86154.39",
    "lastPrice": "82918.53", "markPrice": "86134.55", "indexPrice": "86134.55",
    "openInterest": "0.012139", "priceChangePercent24h": "0.24",
    "timestamp": 1790094770443
  },
  "timestamp": 1790094770443
}

The subscription key is channel:market. seq increments per subscription — a gap means you missed a frame and should resynchronize from REST. The server also pushes unsolicited {"type":"heartbeat"} frames.

Channels

ChannelNeeds marketNeeds auth
perp.ticker✓
perp.orderbook✓
perp.trades✓
perp.kline✓ (+ interval)
perp.prices
perp.funding✓
perp.insurance
pm.markets / pm.orderbook / pm.tradesvaries
user.orders✓
user.trades✓
user.positions✓
user.balances✓
user.margin✓
user.advanced_orders✓
pm.positions / pm.orders✓

perp.orderbook accepts depth; perp.kline requires interval. Private channels accept an optional subAccountId — omit it to receive events across every sub-account on the wallet.

There is no options channel. Options state comes from REST: poll GET /api/v1/options/chain for marks and GET /api/v1/options/positions for your book.

Authenticating the socket

Send an auth frame before subscribing to any user.* channel. The signature covers a fixed payload — timestamp, method, path, no body:

payload = <timestamp> + "GET" + "/api/v1/ws"
{
  "id": "auth-1",
  "method": "auth",
  "params": {
    "apiKey": "1024_…",
    "timestamp": 1790094770000,
    "signature": "<hex HMAC_SHA256(secret, payload)>"
  }
}

Failures come back as AUTH_INVALID (11002) or AUTH_INVALID_SIGNATURE (11003), with the same meanings as on REST.

Connection hygiene

  • Send a frame — ping will do — at least every 90 seconds. A sweeper runs on a 30 s tick against a 90 s idle threshold, so an idle socket is dropped somewhere between ~91 s and 120 s. Do not tune your keepalive to exactly 90 s.
  • Tier-0 ceilings: 3 connections, 50 subscriptions, 50 messages/second. Exceeding any of them is RATE_LIMIT_WS (19010).
  • Reconnect with backoff and re-subscribe — subscriptions do not survive a reconnect. After any gap, resynchronize state from REST before trusting incremental frames again.

Minimal client

import asyncio, hashlib, hmac, json, time, websockets

async def main():
    async with websockets.connect("wss://api-mainnet.1024ex.com/api/v1/ws") as ws:
        ts  = int(time.time() * 1000)
        sig = hmac.new(SECRET.encode(), f"{ts}GET/api/v1/ws".encode(), hashlib.sha256).hexdigest()
        await ws.send(json.dumps({"id": "auth", "method": "auth",
                                  "params": {"apiKey": KEY, "timestamp": ts, "signature": sig}}))
        await ws.send(json.dumps({"id": "1", "method": "subscribe",
                                  "params": {"channel": "perp.ticker", "market": "BTC-USDC"}}))
        await ws.send(json.dumps({"id": "2", "method": "subscribe",
                                  "params": {"channel": "user.positions"}}))

        async def keepalive():
            while True:
                await asyncio.sleep(30)
                await ws.send(json.dumps({"id": "ping", "method": "ping"}))

        asyncio.create_task(keepalive())
        async for raw in ws:
            msg = json.loads(raw)
            if msg.get("type") != "heartbeat":
                print(msg.get("channel"), msg.get("event"), msg.get("data"))

asyncio.run(main())

Did this page help you?