WebSocket API
Real-time order book, trades, candles, tickers and private account channels over Socket.IO.
The real-time API uses Socket.IO (v4), not a raw WebSocket, so connect with a Socket.IO client.
| URL | wss://arc-testnet-api.realtime.exchange |
| Path | /socket.io |
import { io } from 'socket.io-client';
const socket = io(WSS_URL, { path: '/socket.io', transports: ['websocket'] });
socket.on('connected', (info) => console.log('connected', info.id));
socket.on('orderbook', (msg) => console.log(msg.data.bids, msg.data.asks));
socket.emit('subscribe', 'orderbook:BTC-PERP');
Subscribing
| Send | To |
|---|---|
socket.emit('subscribe', '<channel>') | subscribe to one channel |
socket.emit('subscribe', { channel, params }) | subscribe with parameters |
socket.emit('message', { op: 'subscribe', data: ['<channel>', …] }) | subscribe to several channels at once |
socket.emit('unsubscribe', '<channel>') | leave a channel |
socket.emit('message', { op: 'auth', data: { key, secret } }) | authenticate for private channels (Authentication) |
The server first sends the channel's current state, then a subscribed acknowledgement. Updates follow on an event named after the channel kind (orderbook, trades, candles, ticker, index, markprice, positions, orders, …). The exception is ticker-frontend:*, which receives ticker events. unsubscribe is acknowledged with unsubscribed.
- Initial state:
orderbookandcandlessend a snapshot (data.type: "snapshot"; candles send up to 500 bars).tradesreplays up to 50 recent trades as individualtradesmessages.
- Message shape:
- Most messages are
{ channel, data, timestamp }. orderbook,tradesandcandlesmessages addmarket(candles also addresolution); per-market ticker messages addmarketset to the instrument id;indexandmarkpricemessages carry onlychannel,dataandtimestamp. Private channels addaccount.- On private channels,
channelis the bare kind (for example"positions"). The exception isnotifications, whosechannelis the full roomnotifications:{chainId}:{account}; its snapshot isdata: {snapshot: [latest 20]}. ticker:allmessages are{ data, timestamp }.
- Most messages are
- How updates arrive: public channels are pushed on a timer:
orderbookevery 2 s andticker/index/markpriceevery 5 s as full state, andtradesevery 3 s with only the trades not sent before. Private channels receive two kinds of message on the same event:- Periodic full state:
ordersevery 3 s;positions,account,portfolioand the history channels every 10 s. - Event messages as soon as the indexer reports a change:
positions,accountandportfolioreceivedata: {action, sourceAction, position}(actionisposition_opened,position_updated,position_closedorliquidated), andposition-historyreceives the same message forposition_closedandliquidated;ordersandorder-historyreceivedata: {action, order}. Check fordata.actionbefore treating a message as full state. candles: on each fill. A live update carries one bar indata; several can share the sametimeas the bar grows, so replace the bar with thattimerather than appending.
- Periodic full state:
- Errors while subscribing: after
SNAPSHOT_FAILEDyou stay subscribed (live updates follow). After any other error code (for exampleUNKNOWN_INSTRUMENT,INVALID_RESOLUTION,INVALID_CHANNEL,AUTH_REQUIRED,ACCOUNT_MISMATCH,SUBSCRIPTION_LIMIT,RATE_LIMITED) you are not subscribed. Thesubscribed/unsubscribedacks also carrykindandrequested. - Browser origins: connections from a browser are accepted only from the deployment's allowed origins.
- Reconnecting: authentication and subscriptions belong to one connection and are not restored by Socket.IO. On every (re)connect, send
op: authagain, then resubscribe. Each subscribe re-sends the snapshot, so replace your local state with it.
Channel names are normalised: 0x addresses are lowercased, and symbols and assets are uppercased. So btc-perp and BTC-PERP are the same channel. A channel name longer than 128 characters is refused with INVALID_CHANNEL.
Public channels
| Channel | Content |
|---|---|
orderbook:{MARKET} | Order book snapshot {type: "snapshot", bids, asks} |
trades:{MARKET} or trades:all | Each new trade: {id, market, price, amount, side, is_buyer_taker, timestamp, tx_hash} |
candles:{MARKET}:{RESOLUTION} | Candle snapshot, then live updates. Resolutions: 1, 5, 15, 30, 60, 120, 240, 1D, 1W, 1M |
ticker:{MARKET} or ticker:all | Ticker updates |
ticker:{ASSET}:{TYPE}, ticker-frontend:{ASSET}:{TYPE} | Ticker for an asset and instrument type, e.g. ticker:BTC:PERPETUAL |
index:{ASSET} | Index price |
markprice:{ASSET}:PERPETUAL | Mark price |
orderbook levels (price, amount/quantity) and the price in trades messages are raw on-chain integers: divide prices by 10^quote_decimals and sizes by 10^base_decimals from GET /markets (both are 18 for perpetuals). A trade's amount is already scaled. A trade's data.timestamp is Unix seconds, while the message's outer timestamp is milliseconds. data.market is the on-chain pool address; the message's outer market is the channel's market.
Private channels
Authenticate first. You can only subscribe to your own account; any other account is refused with ACCOUNT_MISMATCH. You may omit the account (for example positions); the authenticated account is used.
account:{account}, portfolio:{account}, orders:{account}, positions:{account}, order-history:{account}, trade-history:{account}, position-history:{account}, and notifications:{account}.
Errors
Errors arrive on the error event: { code, message }, plus, where relevant, channel, kind, requested (an echo of what you sent), market, resolution, retryAfterMs or limit.
| Code | Default message |
|---|---|
INVALID_CHANNEL | Invalid channel |
UNKNOWN_CHANNEL | Unknown channel |
INVALID_PARAMS | Invalid parameters |
AUTH_REQUIRED | Authentication required for private channel |
ACCOUNT_MISMATCH | Cannot subscribe to another account |
UNKNOWN_INSTRUMENT | Unknown instrument |
INVALID_RESOLUTION | Unsupported resolution |
SNAPSHOT_FAILED | Failed to fetch snapshot |
RATE_LIMITED | Too many requests |
SUBSCRIPTION_LIMIT | Subscription limit reached |
REQUEST_CANCELLED | Subscribe request cancelled |
INTERNAL | Internal error |
Limits
Default limits:
| Limit | Default |
|---|---|
| Subscriptions per connection | 50 |
| Subscribe requests per second per connection (sustained; each channel in a bulk message counts) | 10 |
| Subscribe burst allowance per connection | 30 |
| Channels per bulk subscribe message | 20 |
| Connections per IP address | 50 |
| Maximum message size (bytes) | 16000 |
Subscription and connection limits run in observe mode by default: breaches are counted, not refused, until the operator switches to enforce. The message size limit is always enforced: a larger message closes the connection. Build your client to stay within all the limits.