$EX5 pre-sale is open — Seed tier · 500M fixed supply · 50% burn · KYC via EX5 ExchangeJOIN →
EX5 API · REST & WEBSOCKET

WebSocket streams

Stream tickers, trades, order-book updates and candles in real time, and your own orders, trades and balances on the private endpoint.

Connect

Publicwss://exchange.ex5.com/api/v2/ranger/public/?stream=…
Privatewss://exchange.ex5.com/api/v2/ranger/private/?stream=… (API-key headers on the handshake)

Pass streams in the URL — repeat stream or separate names with commas — or subscribe after connecting. Every server message is a JSON object with a single key: the stream name, or success / error.

const ws = new WebSocket(
  "wss://exchange.ex5.com/api/v2/ranger/public/?stream=global.tickers&stream=btcusdt.trades",
);
ws.onmessage = (event) => {
  const message = JSON.parse(event.data); // {"<stream>": payload}
  console.log(message);
};
// Add or remove streams at any time:
ws.onopen = () => ws.send(JSON.stringify({ event: "subscribe", streams: ["btcusdt.ob-inc"] }));

Subscribe and unsubscribe

Messages
→ {"event":"subscribe","streams":["ethusdt.kline-1h"]}
← {"success":{"message":"subscribed","streams":["global.tickers","btcusdt.trades","ethusdt.kline-1h"]}}

→ {"event":"unsubscribe","streams":["btcusdt.trades"]}
← {"success":{"message":"unsubscribed","streams":["global.tickers","ethusdt.kline-1h"]}}

The confirmation lists all streams you are subscribed to. An unknown event answers {"error":"Could not parse Type: Invalid event"}. Unknown market names are accepted without an error, so check your spelling.

Public streams

global.tickers

24-hour statistics of every market, about every 5 seconds. All values are strings.

Example
{"global.tickers":{"btcusdt":{"amount":"0.1473","at":"1790530000","avg_price":"84485.51","high":"85117.63","last":"84418.00","low":"83838.87","open":"84074.99","price_change_percent":"+0.41%","volume":"12450.33"}}}

<market>.trades

New trades in one market. date is Unix seconds; price and amount are strings.

Example
{"btcusdt.trades":{"trades":[{"tid":1790530000123,"taker_type":"buy","date":1790530000,"price":"84418.00","amount":"0.0015"}]}}

<market>.ob-inc

Incremental order book. On subscribe you first receive a full snapshot on <market>.ob-snap, then one message per changed price level.

Example
{"btcusdt.ob-snap":{"asks":[["84420.10","0.0350"],["84425.00","0.1200"]],"bids":[["84415.50","0.0800"],["84410.00","0.2500"]],"sequence":157086}}
{"btcusdt.ob-inc":{"asks":["84420.10",""],"sequence":157087}}
{"btcusdt.ob-inc":{"bids":["84416.00","0.0500"],"sequence":157088}}

<market>.kline-<period>

Candles: [time, open, high, low, close, volume] as numbers. Periods: 1m 5m 15m 30m 1h 2h 4h 6h 12h 1d 3d 1w.

Example
{"btcusdt.kline-1h":[1790528400,84300.12,84480.0,84250.35,84418.0,1.2345]}

Keeping a local order book

  1. Subscribe to <market>.ob-inc and store the <market>.ob-snap snapshot and its sequence.
  2. Apply each increment whose sequence is exactly the previous one plus 1. A price with an empty amount removes that level; any other amount replaces it.
  3. If a sequence number is skipped, unsubscribe and subscribe again to receive a fresh snapshot.

Private streams

Connect to the private endpoint with the same three headers used for REST (X-Auth-Apikey, X-Auth-Nonce, X-Auth-Signature) on the handshake request. Browsers cannot set these headers, so use a server-side client.

import asyncio
import hashlib
import hmac
import json
import time

import websockets  # websockets 14+ (older versions: extra_headers=)

API_KEY = "your-api-key-id"
API_SECRET = "your-api-secret"


async def main():
    nonce = str(int(time.time() * 1000))
    signature = hmac.new(API_SECRET.encode(), (nonce + API_KEY).encode(), hashlib.sha256).hexdigest()
    headers = {"X-Auth-Apikey": API_KEY, "X-Auth-Nonce": nonce, "X-Auth-Signature": signature}
    url = "wss://exchange.ex5.com/api/v2/ranger/private/?stream=order&stream=trade&stream=balances"
    async with websockets.connect(url, additional_headers=headers) as ws:
        async for message in ws:
            print(json.loads(message))


asyncio.run(main())
StreamPayload
orderYour order changes: id, uuid, market, side, order_type, price, avg_price, origin_volume, remaining_volume, executed_volume, state, trades_count, created_at, updated_at; a rejected order also carries reason.
tradeYour fills: id, price, amount, total, market, side, taker_type, created_at, order_id, order_uuid.
balancesAll your balances as {"<currency>":["<balance>","<locked>"]}.
deposit_addressA newly generated deposit address.
deposit, withdrawDeposit and withdrawal status changes: type, state, currency, amount, tid, txid, at.

Limits

  • A message you send may be at most 512 bytes; larger messages close the connection with code 1009. Subscribe in batches if you need many streams.
  • The server pings every 54 seconds and closes connections that stay silent for 60 seconds. Standard clients answer pings automatically.
  • Open WebSocket connections count toward the limit of 100 concurrent connections per IP address.