WebSockets
Live market-data streams over WebSocket for the Aftermath Perpetuals API.
The Aftermath Perpetuals API exposes two live streams over WebSocket:
wss://aftermath.finance/api/perpetuals/ws/updates
Unified live-updates proxy for multiplexing multiple perpetuals streams on a single socket
wss://aftermath.finance/api/perpetuals/ws/market-candles/{market_id}/{interval_ms}
Live OHLCV candle stream for one market
The /ws/updates stream is bidirectional — the client sends subscribe / unsubscribe messages and the server pushes JSON frames on each update. The /ws/market-candles stream is push-only; the market and interval come from the URL and no subscribe message is required.
Both endpoints are visible in the Swagger UI under the perpetuals tag. The Swagger page only documents the HTTP upgrade handshake — the frame schemas below are the actual wire protocol observed in production.
/api/perpetuals/ws/updates
/api/perpetuals/ws/updatesFrame model
Client → Server: JSON-encoded
ClientSubscriptionMessage— one per subscribe / unsubscribe action.Server → Client: JSON-encoded
ClientWsEnvelope— one top-level key per frame naming the stream that produced it.Subscription ack: immediately after a successful
subscribe, the server sends a non-JSON plain-text frame starting withSubscription established. ...summarising current subscriptions. Clients should tolerate frames that do not parse as JSON and ignore them.Runtime error frames:
{ "error": "<kind>", "detail": "<human-readable>" }. After a runtime error frame the server closes the socket.Failed upgrades: if the HTTP upgrade itself fails, the server returns a normal HTTP
400with the standard JSONErrorResponsepayload (error_code,message,short_message).
Subscribing
Send a message of the shape below on open. subscriptionType is a tagged object — exactly one stream key per message.
{
"action": "subscribe",
"subscriptionType": {
"topOfOrderbook": {
"marketId": "0x<market-id>",
"priceBucketSize": 0.0001,
"bucketsNumber": 10
}
}
}Use "action": "unsubscribe" with the same subscriptionType payload to stop a stream without closing the connection. A single socket can hold multiple subscriptions at once — send one message per stream you want to add.
Integer serialisation
Several fields in the market frame (and elsewhere) come as decimal strings with a trailing n — for example "3600000n" or "100n". These represent u128 / i128 values that exceed safe JavaScript number range. Strip the n and parse as BigInt or decimal string on receipt. Fixed-precision decimals such as prices and sizes are serialised as JSON numbers.
Stream types
topOfOrderbook — bucketed top-of-book snapshots
topOfOrderbook — bucketed top-of-book snapshotsSubscribe:
marketId
string
Hex market id (e.g. the id field returned by GET /api/ccxt/markets).
priceBucketSize
number
Price-tick grouping. Pass the market's tick (e.g. 0.0001) for ungrouped levels.
bucketsNumber
integer
Levels per side. Verified values: 10, 25, 50, 100, 200, 500.
Server frame:
Each level carries the per-level size / sizeUsd plus the running cumulative totalSize / totalSizeUsd through that level. Frames are full snapshots of the top bucketsNumber levels per side, not deltas. Level order is not guaranteed — sort asks ascending and bids descending on receipt.
orderbook — full orderbook deltas
orderbook — full orderbook deltasSubscribe:
Server frame:
Deltas are additive: positive size adds liquidity to the level, negative size removes it, and a level that reaches zero size is considered cleared. Use the rising nonce to order deltas and detect gaps. Seed from POST /api/ccxt/orderbook before applying deltas; on gap, re-fetch the snapshot and resume.
If you are using the TypeScript SDK rather than the raw wire protocol, prefer its subscribeOrderbook({ marketId }) helper and let the SDK manage the current subscription payload shape.
oracle — aggregated oracle price updates
oracle — aggregated oracle price updatesSubscribe:
Server frame:
basePrice is the base-asset oracle price; collateralPrice is the collateral oracle price (USDC ≈ 1). Frames arrive on each oracle tick — typically several times per second on active markets.
Aftermath's oracle aggregator sources each market from one of several providers (Pyth, Stork, SEDA, Switchboard). The WebSocket frame is provider-agnostic — it always delivers the aggregated price the protocol uses on-chain. To see which provider backs a given market, consult the feed id in the market frame's marketParams.basePriceFeedId (cross-referenced with the tables under Market Specifications) or the Oracles page.
market — market configuration and state
market — market configuration and stateSubscribe:
A full snapshot is pushed on subscribe; further frames arrive when market state changes (funding, open interest, etc.).
Server frame (fields abridged for brevity; the full list mirrors the on-chain Market object):
Use this stream if you need live indexPrice, openInterest, or the raw funding inputs without polling /api/perpetuals/markets/* REST endpoints. For funding calculations, derive the live rate from marketState.premiumTwap / indexPrice; do not treat estimatedFundingRate as the canonical 8h funding value.
Account and execution streams
The same /ws/updates socket also carries account- and execution-scoped updates. The OpenAPI description summarizes these broadly as user and order updates, while the current TypeScript SDK exposes higher-level helpers such as subscribeUser, subscribeMarketOrders, subscribeUserOrders, and subscribeUserCollateralChanges.
Treat those SDK helpers as the source of truth for these variants rather than hard-coding raw JSON from stale examples. Some account-scoped variants also require additional signed auth material, for example withStopOrders when including encrypted stop-order data.
Error frame
The socket is closed after a runtime error frame. Clients should treat close after an error as final and reconnect from a known-good state.
Multiplexing
A single /ws/updates socket can carry any mix of the stream types above. Send one subscribe message per stream; frames arrive interleaved, keyed by the envelope top-level field. Unsubscribe from individual streams without closing the socket.
/api/perpetuals/ws/market-candles/{market_id}/{interval_ms}
/api/perpetuals/ws/market-candles/{market_id}/{interval_ms}Push-only candle stream. The path encodes the market and bucket width; no client message is sent.
streams one-minute candles for that market. Server frame:
lastCandle is the rolling current bucket — its fields mutate with every push until the bucket closes, at which point a fresh lastCandle for the next bucket starts. timestamp is the UTC epoch-millis start of the bucket. No subscription ack is sent on this endpoint. Runtime errors, when present, use the same { "error", "detail" } JSON shape as /ws/updates; invalid path parameters fail the HTTP upgrade with a 400 before the socket is established.
Client examples
Bun / TypeScript
Python
Operational guidance
Snapshot before delta: for
orderbookdeltas, seed fromPOST /api/ccxt/orderbookbefore applying frames so a reconnect replaces rather than merges. FortopOfOrderbookeach frame is already a full snapshot of the top-N, so no REST seed is required.Use the
nonce: theorderbookstream'snoncestrictly increases. Treat a gap as a resync signal — drop local state and re-fetch the snapshot.Reconnect with backoff: exponential backoff starting at 1s, capped at 30s. Re-send every subscription on each successful
open.Detect silent sockets: keep a watchdog that falls back to REST polling if no frames arrive for ~5s on an open socket.
oraclein particular updates many times per second on active markets; prolonged silence means the socket is stuck.Ignore unrecognised envelope keys: the envelope is extensible — new top-level stream keys may be added. Route on the key you subscribed to and ignore the rest.
Public endpoint: the
/ws/updatesproxy is public and does not require authentication on the socket itself. Account-scoped streams are keyed by public account identifiers, but some optional account data exposed by the SDK (such as stop-order details) requires signed auth material in the subscription payload.
Last updated