WebSockets

The exchange pushes real-time updates over a WebSocket endpoint at /ws on the same host as the REST API:

wss://api.testnet.mnx.fi/ws   (testnet)
wss://api.app.mnx.fi/ws      (production)

The protocol is topic-based publish/subscribe: you send a SUBSCRIBE message listing topics, and the server pushes a JSON message whenever an event occurs on one of them.

Subscribing

const ws = new WebSocket('wss://api.testnet.mnx.fi/ws')

ws.onopen = () => {
  ws.send(
    JSON.stringify({
      type: 'SUBSCRIBE',
      topics: ['prices:1', 'market:1:orderbook'],
    })
  )
}

ws.onmessage = (event) => {
  const message = JSON.parse(event.data)
  // Server events: { topics: string[], timestamp, data: { type, ... } }
  // Control acks:  { type: 'SUBSCRIBED' | 'SUBSCRIBE_REJECTED' | ... }
}

The server acknowledges with { "type": "SUBSCRIBED", "topics": [...] }. Topics you are not allowed to subscribe to come back in a SUBSCRIBE_REJECTED message instead. Unsubscribe the same way with { "type": "UNSUBSCRIBE", "topics": [...] }. Event messages carry the topics they were published to, a timestamp, and a data object whose type field identifies the event.

Topics

TopicScopeWhat it carries
prices:<market_id>PublicOracle price updates (PRICE_UPDATED).
funding:<market_id>PublicFunding rate updates (FUNDING_RATE_UPDATED). Funding is the periodic payment between longs and shorts that keeps a perpetual's price anchored to the underlying.
market:<market_id>:orderbookPublicAggregated order book level deltas and refresh signals.
market:<market_id>:statsPublicAggregate market statistics.
user:<user_id>PrivateAll events for your account across markets: orders, positions, balances, leverage adjustments, liquidations, deposits, and withdrawals. The recommended single subscription for account state.
deposits:<user_id>PrivateDeposit events only.
orders:<market_id>:<user_id>, positions:<market_id>:<user_id>, liquidations:<market_id>:<user_id>PrivateGranular per-market slices of your account events. Every user-owned event is also emitted to user:<user_id>, so subscribe to these only when you want narrower streams.

Authenticating for private topics

Private topics require the same credentials as the REST API (see Authentication & signing), supplied inside the SUBSCRIBE message — either an authorization field with a full header value ("Bearer <token>" or "Key <api-key>") or an authToken field with a Privy identity token value. UUID session tokens are no longer accepted:

{
  "type": "SUBSCRIBE",
  "topics": ["user:123"],
  "authorization": "Key <api-key>"
}

The authenticated user must match the user_id in the topic; otherwise the topic is rejected. Once a connection has authenticated, later SUBSCRIBE messages on the same connection can omit the credential.

Event types

Key data.type values (amounts in WebSocket payloads are raw 1e18-scaled integers — see Getting started):

  • ORDER_CREATED — an order was accepted, with its id, hash, side, type, price, quantity, and remaining quantity.
  • ORDER_CANCELLED — an order was cancelled, expired, or terminally rejected by batch validation; includes a reason and whether it was partially filled.
  • ORDER_REJECTED — a placement was rejected before acceptance (e.g. insufficient margin).
  • PRICE_UPDATED — a new oracle price for a market.
  • FUNDING_RATE_UPDATED — a new funding rate for a market.
  • POSITION_UPDATED — your position changed on-chain (trade, margin added/removed, or leverage adjusted).
  • LIQUIDATION — a liquidation touched your account (liquidation is the forced closing of a position whose margin no longer covers its risk).
  • ORDERBOOK_LEVEL_DELTA, BATCH_ABORTED, TRADE_BATCH_SETTLED — public order book and batch settlement signals (below).

Keeping an order book in sync

To mirror a market's order book, combine REST snapshots with additive deltas:

  1. Subscribe to market:<market_id>:orderbook.
  2. Fetch a snapshot from GET /v0/orderbook/by-market/:market_id (50 levels per side by default).
  3. Apply ORDERBOOK_LEVEL_DELTA events additively on top. Each delta carries a deltaId for duplicate-delivery protection and a readAfter coordinate; a snapshot already includes all deltas at or below its sequence, so do not re-apply older deltas after a snapshot lands.
  4. On BATCH_ABORTED— the coarse refresh signal for paths that cannot be expressed as a precise level delta — refetch the snapshot (using the event's readAfter).

Connection lifecycle

  • The server sends protocol-level WebSocket ping frames every 30 seconds and drops connections that stop answering. Standard WebSocket clients answer these automatically; no application code is needed. An application-level { "type": "PING" } / { "type": "PONG" } exchange is also available for testing.
  • Before a planned restart (deploys), the server sends a SERVER_RESTARTING message and closes with WebSocket close code 1012. Reconnect and re-send your subscriptions — they are not persisted across connections.