WebSocket API (Beta)
Three multiplexed endpoints, all JSON text frames. Open one connection to each you need and subscribe to any number of channels on it. /ws and /api-ws use camelCase fields, and the TypeScript SDK wraps them with typed payloads and automatic reconnection. /ws-rtds relays an external price feed in its own format; see Crypto prices.
| Carries | Mainnet | Testnet | |
|---|---|---|---|
/ws | Node stream: books, trades, account state, chain data | wss://rpc.txflow.com/ws | wss://rpc.txflow-testnet.xyz/ws |
/api-ws | API-service stream: tickers, trade lists, candles, history snapshots, async Outcome failures | wss://rpc.txflow.com/api-ws | wss://rpc.txflow-testnet.xyz/api-ws |
/ws-rtds | Crypto reference prices for Outcome underlyings: spot, Chainlink, TWAP | wss://rpc.txflow.com/ws-rtds | wss://rpc.txflow-testnet.xyz/ws-rtds |
Connection & control plane
A connection carries subscribe, unsubscribe, and ping frames from the client. On /ws, put the channel's subscribe type at the top level as well as inside subscription — the node routes on the top-level type and silently drops a frame without it:
{ "method": "subscribe", "type": "orderUpdates", "subscription": { "type": "orderUpdates", "user": "0x…" } }/api-ws takes the subscription object alone.
Keep-alive
Send a ping every 10 seconds on every endpoint, starting when the socket opens. The gateway closes a /ws or /api-ws connection that has sent nothing for about 16 seconds (close code 1006); /ws-rtds allows 60 seconds. Server pushes do not count as activity.
| Endpoint | Client sends | Server replies |
|---|---|---|
/ws | {"method":"ping"} | {"method":"PONG"} |
/api-ws | {"method":"ping"} | {"channel":"pong"} |
/ws-rtds | PING (plain text, not JSON) | PONG (plain text) |
Drop the pong before dispatching messages by channel. Any client frame counts as activity, but use a dedicated timer so a quiet connection stays up.
const ws = new WebSocket('wss://rpc.txflow-testnet.xyz/ws')
let heartbeat: ReturnType<typeof setInterval> | undefined
ws.onopen = () => {
ws.send(JSON.stringify({ method: 'subscribe', type: 'trades', subscription: { type: 'trades', coin: '1' } }))
heartbeat = setInterval(() => ws.send(JSON.stringify({ method: 'ping' })), 10_000)
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.method === 'PONG' || msg.channel === 'pong') return // heartbeat reply
// handle msg.channel / msg.data
}
ws.onclose = () => clearInterval(heartbeat) // then reconnect and re-subscribeThe TypeScript SDK pings every 10 seconds on its own (keepAlive.interval).
A single connection multiplexes every channel. On /ws, every subscribe and unsubscribe frame repeats the channel type at the top level ("type") — the node dispatches on it and ignores a frame without it. The client sends subscribe / unsubscribe / ping frames; the server replies to ping with pong. On /ws there is no subscribe acknowledgement — invalid subscriptions are silently ignored. /api-ws acknowledges each subscribe with a subscriptionResponse frame and answers an invalid one with {"channel":"error","message":…}.
{
"method": "subscribe",
"type": "l2Book",
"subscription": {
"type": "l2Book",
"coin": "4"
}
}{
"method": "unsubscribe",
"type": "l2Book",
"subscription": {
"type": "l2Book",
"coin": "4"
}
}{
"method": "ping"
}Case-insensitive. Canonical lowercase form shown.
"subscribe""unsubscribe""ping"Alias for `type` (either key name is accepted).
Tagged union discriminated by `type`. One of the data-bearing subscription kinds documented here.
"orderUpdates"User whose orders to track. Use the account address, not an agent's.
Optional trading-pair filter. Omitted or "" means all coins.
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"trades"Optional filter: only trades where this address is taker or maker are pushed. Omit or "" for all trades. Use the account address, not an agent's.
"l2Book"Optional significant-figure aggregation.
Optional explicit price tick.
Depth and price granularity are fixed server-side.
"l2BookDepth""positionUpdates"User to follow. Use the account address, not an agent's.
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"activeAssetCtx""activeAssetData"User to follow. Use the account address, not an agent's.
"userAssetData"User to follow. Use the account address, not an agent's.
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"webData2"User to follow. Use the account address, not an agent's.
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"allMids"Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"clearinghouseState"User to follow. Use the account address, not an agent's.
Optional perp dex name. Defaults to the first dex.
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"bbo""allAssetCtxs"Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"userFundings"User to follow. Use the account address, not an agent's.
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"twapStates"User to follow. Use the account address, not an agent's.
Defaults to "" (first/default dex).
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"userTwapHistory"User to follow. Use the account address, not an agent's.
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"userTwapSliceFills"User to follow. Use the account address, not an agent's.
Filter by an instrument's configured exchange_id. Same three-state semantics as the HTTP /info ExchangeScope: missing, null, or 0 -> Default (only instruments with no configured exchange_id); the string "all" (case-insensitive) -> All (no filter, every exchange); a positive integer n -> Exchange(n) (only that exchange_id).
"deploy""outcomeState"User to follow. Use the account address, not an agent's.
The channel's subscribe type, e.g. `orderUpdates`. Required on every subscribe and unsubscribe. Case-insensitive. Alias: `action`.
"PONG"Channels
Grouped like the Info API. Each page lists the subscription parameters and the pushed payload, and has a Try it panel that subscribes live.
Shared
Books, trades, and order updates take a coin. The explorer channels stream the chain itself and take no parameters.
| Channel | Use it to |
|---|---|
trades | Drive the public trade tape for one instrument. |
l2Book | Drive an order-book widget for one instrument; each push is a full snapshot. |
l2BookDepth | Maintain a local fixed-depth book from one snapshot plus deltas (lower bandwidth, you apply updates). |
bbo | Stream best bid / best ask for one instrument (spread display, quoting bots). |
allMids | Stream the mid price of every instrument — the lightest way to keep a ticker live. |
orderUpdates | Track a user’s orders live: placed, filled, cancelled, rejected. |
block | Feed an explorer’s latest-blocks list. |
tx | Feed an explorer’s latest-transactions list. |
newBlock | Tick the current block height once per second. |
Perpetuals
Mark, oracle and funding context, position and margin state, and the TWAP streams.
| Channel | Use it to |
|---|---|
activeAssetCtx | Keep one market header live: mark, oracle, funding, OI (Outcome: price, volume, OI). |
allAssetCtxs | Keep a perp markets table live — every instrument’s context in one subscription. |
activeAssetData | Keep the order ticket’s leverage / max size / available margin live for one instrument. |
userAssetData | Same as activeAssetData, for every instrument in one subscription. |
clearinghouseState | Keep the Positions panel and margin summary live. |
position | React to individual position changes (opened, resized, closed). |
webData2 | One-subscription account view: positions + margin + open orders (Hyperliquid-compatible). |
userFundings | Append funding settlements to Funding History as they happen. |
twapStates | Show progress of a user’s running TWAP orders. |
userTwapHistory | Append TWAPs to the history list as they finish or are cancelled. |
userTwapSliceFills | Stream TWAP child fills as they execute. |
Outcome
Dedicated batch-BBO, trade, and order-lifecycle channels, plus the shared single-market streams with the numeric instrument_id as coin. outcomeTrades follows several instruments in one subscription; trades follows one market. There is no Outcome balance channel — poll clearinghouseState with accountType: "outcome".
| Channel | Use it to |
|---|---|
outcomeBbos | Keep prices live in an Outcome discovery grid or watchlist (up to 50 instruments per subscription). |
outcomeOrderUpdates | Track the matching lifecycle of a user’s Outcome orders: filled, rejected, cancelled. |
outcomeTrades | One public tape across several Outcome instruments (every leg of an event). |
l2Book | Drive an order-book widget for one instrument; each push is a full snapshot. |
trades | Drive the public trade tape for one instrument. |
activeAssetCtx | Keep one market header live: mark, oracle, funding, OI (Outcome: price, volume, OI). |
API service
Subscribe on /api-ws, not /ws.
| Channel | Use it to |
|---|---|
ticker | Show last price, 24-hour change, high/low, and volume for one instrument. |
trades | Show the latest 300 trades for one instrument from /api-ws, refreshed every few seconds. |
candle | Keep the last bar of a chart live after seeding it with candleSnapshot. |
multiCandle | Keep the last bar of up to 8 coins live on one subscription, e.g. a markets list with sparklines. |
outcomeOrderUpdate | Catch async Outcome order submissions that fail (by traceId). Only failures are pushed; not a fill signal. |
userHistoricalOrders | Load a user’s order history over /api-ws — one snapshot, no updates. |
userFills | Load a user’s fill history over /api-ws — one snapshot, no updates. |
webData2 | Load a user’s open orders over /api-ws — one snapshot, no updates. |
userHistoricalOrders, userFills, and webData2 on /api-ws send one snapshot on subscribe and nothing after.
Crypto prices (/ws-rtds)
Reference prices for crypto Outcome underlyings, relayed from Polymarket's real-time data service. Frames use snake_case fields and have no channel envelope.
{
"action": "subscribe",
"subscriptions": [
{ "topic": "crypto_prices", "type": "update", "filters": "btcusdt,ethusdt" },
{ "topic": "crypto_prices_chainlink", "type": "update", "filters": "{\"symbol\":\"btc/usd\"}" }
]
}unsubscribe takes the same shape.
| Topic | Price | filters | Symbols |
|---|---|---|---|
crypto_prices | Binance spot | Comma-separated: "btcusdt,ethusdt" | btc, eth, sol, xrp, doge, bnb, hype + usdt |
crypto_prices_chainlink | Chainlink | JSON: "{\"symbol\":\"btc/usd\"}" | The above plus zec, as <asset>/usd |
crypto_prices_twap_thirty | 30s TWAP of Chainlink | Same as Chainlink | Same as Chainlink |
crypto_prices_twap_sixty | 60s TWAP of Chainlink | Same as Chainlink | Same as Chainlink |
Each symbol pushes about once a second:
{
"topic": "crypto_prices_chainlink",
"type": "update",
"timestamp": 1792047647886,
"payload": {
"symbol": "btc/usd",
"timestamp": 1792047647000,
"value": 80856.15721525438,
"full_accuracy_value": "80856157215254380000000"
}
}Limits: 20 connections per IP, 128 symbol subscriptions per connection, 64 subscriptions per frame.