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
| Public | wss://exchange.ex5.com/api/v2/ranger/public/?stream=… |
| Private | wss://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"] }));import asyncio
import json
import websockets # pip install websockets
async def main():
url = "wss://exchange.ex5.com/api/v2/ranger/public/?stream=global.tickers&stream=btcusdt.trades"
async with websockets.connect(url) as ws:
await ws.send(json.dumps({"event": "subscribe", "streams": ["btcusdt.kline-1m"]}))
async for message in ws:
print(json.loads(message))
asyncio.run(main())Subscribe and unsubscribe
→ {"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.
{"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.
{"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.
{"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.
{"btcusdt.kline-1h":[1790528400,84300.12,84480.0,84250.35,84418.0,1.2345]}Keeping a local order book
- Subscribe to
<market>.ob-incand store the<market>.ob-snapsnapshot and itssequence. - Apply each increment whose
sequenceis exactly the previous one plus 1. A price with an empty amount removes that level; any other amount replaces it. - 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())import { createHmac } from "node:crypto";
import WebSocket from "ws"; // npm install ws — browsers cannot set these headers
const API_KEY = "your-api-key-id";
const API_SECRET = "your-api-secret";
const nonce = String(Date.now());
const signature = createHmac("sha256", API_SECRET).update(nonce + API_KEY).digest("hex");
const ws = new WebSocket("wss://exchange.ex5.com/api/v2/ranger/private/?stream=order&stream=trade&stream=balances", {
headers: { "X-Auth-Apikey": API_KEY, "X-Auth-Nonce": nonce, "X-Auth-Signature": signature },
});
ws.on("message", (data) => console.log(JSON.parse(data)));| Stream | Payload |
|---|---|
order | Your 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. |
trade | Your fills: id, price, amount, total, market, side, taker_type, created_at, order_id, order_uuid. |
balances | All your balances as {"<currency>":["<balance>","<locked>"]}. |
deposit_address | A newly generated deposit address. |
deposit, withdraw | Deposit 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.