Skip to main content

WebSocket: Unified Account Channel

Private WebSocket channel that pushes live uniMMR updates, margin calls, reduce-only transitions, and liquidation steps to a single user.

Endpoint

wss://api.prex.world/ws

Authentication is JWT (same as positions / orders / balance channels). Subscribe with a token inline or after a prior auth_token message.

Subscribe

{ "type": "subscribe", "channel": "unified_account", "token": "<JWT>" }

Server replies with:

{ "type": "subscribed", "channel": "unified_account" }

Push Cadence

While the user is in unified mode, the risk worker emits exactly one event per 2 s tick (even when nothing changes) so the front-end can render live uniMMR without polling. On a quiet tick this is the baseline update event. On a tick where the status transitions, the event type is replaced: you receive a single margin_call / reduce_only / liquidating / status_change event instead of the baseline update, not in addition to it. Likewise, when a liquidation step succeeds in a tick, the tick's event is liquidation_step — it takes the place of a liquidating transition event fired in the same tick.

Event Envelope

{
"channel": "unified_account",
"event": "<event_name>",
"data": {
"user_address": "0x...",
"event": "<event_name>",
"uni_mmr": "1.85",
"total_equity": "52000.00",
"available_balance": "2000.00",
"account_status": "warning_1",
"reason": "uniMMR=1.85 status: normal → warning_1",
"timestamp": 1712678400000
}
}

reason and orders_cancelled are omitted entirely from the JSON when they carry no value — they are never serialized as null. The example above is a margin_call transition, so reason is present but orders_cancelled is absent.

Event kinds

eventTriggerExtra semantics
updateEvery tick (~2 s) with no transition or liquidation stepBaseline heartbeat; reason is omitted
margin_callTransition into warning_1 or warning_2 (replaces the tick's update)reason names old → new status
reduce_onlyTransition into reduce_only (replaces the tick's update)orders_cancelled = number of orders auto-cancelled
liquidatingTransition into liquidating (replaces the tick's update)Replaced by liquidation_step if the first step succeeds in the same tick
liquidation_stepOne position force-closed in this tick (replaces the tick's update / liquidating)reason contains "liquidated {symbol} {side} size_usd={...} pnl={...}"
status_changeAny other transition (e.g. recovering to normal)

Field notes

  • uni_mmr is the decimal value as a string to preserve precision; may be null if the user has no positions.
  • orders_cancelled is only populated on reduce_only / liquidating transitions; on every other event the field is omitted entirely (never sent as null), so use data.get("orders_cancelled")-style access.
  • Per-user fan-out: the server only forwards events where data.user_address matches the subscribed session's authenticated address, so clients do not need to filter.

Example (Python)

import asyncio, json, websockets
from eth_account import Account
from eth_account.messages import encode_typed_data
import requests

# 1. Log in with a wallet private key to obtain a JWT.
acct = Account.from_key("<PRIVATE_KEY>")
addr = acct.address.lower()
typed = requests.get(f"https://api.prex.world/api/v1/auth/nonce/{addr}").json()["typed_data"]
signable = encode_typed_data(full_message=typed)
sig = "0x" + acct.sign_message(signable).signature.hex()
ts = int(typed["message"]["timestamp"])
jwt = requests.post(
"https://api.prex.world/api/v1/auth/login",
json={"address": addr, "signature": sig, "timestamp": ts},
).json()["token"]

# 2. Subscribe.
async def run():
async with websockets.connect("wss://api.prex.world/ws") as ws:
await ws.send(json.dumps({"type": "auth_token", "token": jwt}))
await ws.send(json.dumps({
"type": "subscribe",
"channel": "unified_account",
"token": jwt,
}))
async for msg in ws:
m = json.loads(msg)
if m.get("channel") == "unified_account":
d = m["data"]
print(f"[{m['event']}] uniMMR={d['uni_mmr']} "
f"status={d['account_status']} reason={d.get('reason')}")

asyncio.run(run())