DocsSubscriptions

Subscriptions

After you receive the system ready frame, send UTF-8 JSON text frames with an action field. The server acknowledges successful subscribe and unsubscribe calls with matching system events.

Prefer a guided live subscribe first? Use the playground on the WebSocket overview, then come back here for the full action reference.

subscribe

Send a JSON text frame. channels must be a non-empty array. symbols can be an array or a single comma-separated string — all values are trimmed, uppercased, de-duplicated, and sorted before validation.

ParameterTypeDescription
actionreqstringLiteral "subscribe".
channelsreqstring[]Non-empty array, e.g. ["crypto.quotes", "crypto.trades"].
symbolsreqstring | string[]Non-empty array or comma-separated string. Values are normalized server-side.
snapshotboolean | stringOptional. Defaults to true. false skips the initial snapshot and delivers live updates only; "always" delivers the cached value even when it is no longer recent (see Initial snapshot).
Example — subscribe to two channels
{
  "action": "subscribe",
  "channels": ["crypto.quotes", "crypto.trades"],
  "symbols": ["BTCUSD", "ETHUSD"]
}
Acknowledgement
{
  "type": "system",
  "event": "subscribed",
  "channels": ["crypto.quotes", "crypto.trades"],
  "symbols": ["BTCUSD", "ETHUSD"]
}

Initial snapshot

Immediately after the subscribed acknowledgement, the stream sends an initial snapshot: one last-known quote and/or trade per subscribed symbol that already has a recent value. This gives you a value to display right away instead of waiting for the next live tick. Live updates then follow as the market moves. Symbols without a recent cached value are skipped until their first live tick.

Snapshot frames use the same shape as live updates for that channel family (crypto/forex/stocks use ts + string numerics; indices/ETFs/commodities use timestamp + number numerics). The time field is the original market event time — not the send time. Outside regular hours it can be older than the current clock; that is expected. Compare it against your own receive time to gauge age.

On US and Asian equities, and on ETFs, a cached value is replayed only while it is recent, currently about two minutes. Subscribing to those symbols outside their trading session therefore delivers nothing until the first live tick, so a session-old value never arrives unasked. If you want it anyway, for example while developing against a closed market, subscribe with "snapshot": "always". Snapshot frames stay marked either way, so you can drop them on your own terms. What arrives is the last value the stream holds for that symbol, so shortly after a service restart during a closed session there may be nothing to replay yet; GET /stocks/snapshot/{symbol} is the guaranteed path to a last known value at any hour.

Example — keep the last known value while the market is closed
{
  "action": "subscribe",
  "channels": ["stocks.quotes", "stocks.trades"],
  "symbols": ["US:AAPL"],
  "snapshot": "always"
}

If you only want live updates and never a cached last-known value — for example an on-chain oracle that must not treat a stale value as current at session open — add "snapshot": false to the subscribe message. The connection then receives only live ticks; the first frame you get is a genuine live update. The default (snapshot enabled) is unchanged for every other client.

Example — live updates only (no initial snapshot)
{
  "action": "subscribe",
  "channels": ["indices.quotes", "indices.trades"],
  "symbols": ["JP225", "HK50"],
  "snapshot": false
}

One precedence rule: a subscribe that sets "trade_mode": "full_volume" receives no snapshot at all, whatever snapshot says. A replayed cached trade is not an execution, and counting it would add size that never happened inside the window you are summing.

The stocks.status channel likewise never replays a snapshot: a halt status is a statement about the present, so the stream only delivers events that happen after you subscribe. See Message types for the frame shape.

bonds.quotes is the opposite case: a government bond yield is a daily observation, so the latest published rate is always the current value and is always replayed on subscribe, however old the observation date is. Only "snapshot": false suppresses it. See Message types for the frame shape.

unsubscribe

Same channels and symbols rules as subscribe. Removes the given symbols from the listed channels for this connection. Symbols that remain subscribed on other channels stay active.

Example
{
  "action": "unsubscribe",
  "channels": ["crypto.quotes"],
  "symbols": ["BTCUSD"]
}
Acknowledgement
{
  "type": "system",
  "event": "unsubscribed",
  "channels": ["crypto.quotes"],
  "symbols": ["BTCUSD"]
}

ping / pong

Two keepalive mechanisms apply: a client JSON ping, and native WebSocket protocol pings from the server.

Client → server (JSON)
{ "action": "ping" }
Server → client (JSON pong)
{
  "type": "system",
  "event": "pong",
  "ts": 1743512400000
}

The server also sends native WebSocket ping frames. If the client does not answer with protocol pongs, the server emits { "type": "system", "event": "disconnect", "code": "HEARTBEAT_TIMEOUT" } and closes with code 4008.

Subscription limits

Distinct-symbol and connection caps follow your plan and packages. The per-subscribe message size is a separate server cap.

  • Symbols per subscribe action — each subscribe call is capped by API_PUBLIC_WS_MAX_SYMBOLS_PER_SUBSCRIBE (default 500). This is not a plan field. Send additional subscribe messages if you need more symbols than one call allows.
  • Total distinct symbols on your account — your plan sets how many different symbols you can stream at the same time, across all connections and channels. Your dashboard shows usage and package limits.
  • Available channels — you can subscribe only to the asset classes included in your active packages (for example Crypto, Forex, or Stocks). Add or upgrade a package to unlock additional channels.

Typical defaults by tier are listed on the Rate limits page. Upgrade or add packages if you need higher WebSocket capacity.