WebSocket
Endpoint wss://api.lexx-trade.com/api/v2/ws · AsyncAPI 3.0 · API v2.0.0 ·
download asyncapi.yaml
· Practical client patterns live in the WebSocket guide.
Real-time, per-user streaming for orders, balances, positions, and bot state over a single
authenticated WebSocket. Companion to the REST OpenAPI spec (openapi.yaml).
All messages are JSON objects with an op discriminator. Every server message carries a ts
(Unix ms); client requests may carry a client-chosen id that is echoed back on the reply.
Connection
- Endpoint:
wss://api.lexx-trade.com/api/v2/ws - One socket carries all channels. There are no public (unauthenticated) channels — market data (orderbook/ticker/public trades) is not served here.
- Delivery is per-user: a socket only ever receives events for the account that owns the authenticating key.
Authentication (Ed25519, challenge-response)
- On connect the server sends a one-time
challenge. - The client signs
"WSAuth" + timestamp + challengewith its Ed25519 private key and sends anauthrequest. - The server replies with
auth { authenticated: true, userId }.
keyis the raw 32-byte Ed25519 public key, Base64url (43 chars) — this IS the key identifier.signis the Base64 Ed25519 signature (Base64url also accepted).timestampis Unix ms and must be within ±5000 ms of server time.- The challenge is single-use and connection-bound, so a captured auth frame cannot be replayed. To re-authenticate, reconnect.
- Auth must complete within 5 s of connecting (else the socket is closed,
4001). - If the key has an IP whitelist it is enforced (CIDR) against the real client IP.
- Subscribing requires a valid key with at least read permission.
Limits (DoS backstops — not per subscription tier)
| Limit | Value | On breach |
|---|---|---|
| Connections per client IP | 50 | upgrade rejected 429 |
| Total connections per node | 10 000 | upgrade rejected 503 |
| Max frame size | 64 KiB | socket closed 1009 (message too big; may surface as abnormal 1006 on some clients) |
| Messages per socket | 200 / 10 s | RATE_LIMITED, close 1008 |
| Auth attempts per socket | 5 | TOO_MANY_ATTEMPTS, close 4001 |
| Send-buffer backlog per socket | 4 MiB | socket terminated (slow consumer — abnormal close 1006) |
A client that cannot keep up with its event stream (unread data piles up past 4 MiB) is
dropped without a close frame — read messages promptly and reconnect on 1006.
Unknown op values after authentication are silently ignored (no error reply) — do not
block waiting on the correlation id of an unsupported op.
Heartbeat
The server sends a WebSocket control-ping every 20 s; standard clients auto-reply with a pong and
a socket that misses it is terminated within ~20–40 s. An optional application-level ping/pong
is also supported.
Close codes
| Code | Reason |
|---|---|
1008 | Message-rate limit exceeded |
1009 | Frame exceeded the 64 KiB max size (message too big) |
4001 | Auth timeout, or too many auth attempts |
4003 | API key revoked mid-session |
Reconnecting
The auth challenge is single-use and connection-bound: after any disconnect you must open a
new socket, receive a fresh challenge, re-authenticate, and re-subscribe (subscriptions are
not persisted across connections). Recommended client behavior:
- Reconnect with exponential backoff plus jitter (e.g. 1 s → 2 s → 4 s → … capped at 30 s); reset the backoff after a successful re-auth.
- On close
1008(message-rate limit) reduce your message rate before reconnecting. - On close
4001complete auth faster after connecting (it must finish within 5 s) and check your clock skew (±5000 ms window). - On close
4003the API key was revoked — do not auto-reconnect; obtain a new key first.
Client → Server
Messages the CLIENT sends to the server.
auth Authenticate (client → server) Authenticate the connection using the server-issued challenge.
Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
args required key required Raw 32-byte Ed25519 public key, Base64url (43 chars). This IS the key id.
sign required Base64 Ed25519 signature of "WSAuth" + timestamp + challenge (Base64url also accepted).
timestamp required Unix milliseconds; must be within ±5000 ms of server time.
ping Ping (client → server) Application-level liveness check; the server replies with pong.
Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
subscribe Subscribe (client → server) Subscribe to a private channel. Requires auth + a key with read access.
Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
args required channel required Private channel name.
enum: "orders" · "balance" · "positions" · "botStatus"
exchange Optional filter — narrows delivery to one exchange. Supported on orders, balance, and positions; NOT supported on botStatus (the subscription is ACKed but never delivers).
symbol Optional filter — narrows delivery to one symbol. Supported ONLY on orders and ONLY together with exchange. On other channels (or without exchange) the subscription is ACKed but never delivers.
unsubscribe Unsubscribe (client → server) Remove a previous subscription (same args identify the subscription).
Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
args required channel required Private channel name.
enum: "orders" · "balance" · "positions" · "botStatus"
exchange Optional filter — narrows delivery to one exchange. Supported on orders, balance, and positions; NOT supported on botStatus (the subscription is ACKed but never delivers).
symbol Optional filter — narrows delivery to one symbol. Supported ONLY on orders and ONLY together with exchange. On other channels (or without exchange) the subscription is ACKed but never delivers.
Server → Client
Messages the SERVER sends to the client.
challenge Challenge (server → client) One-time, connection-bound challenge sent on connect. Include it in the signed auth message.
Payload
op required data required challenge required Hex string to include in the signed auth payload.
ts required Server timestamp, Unix milliseconds.
auth Auth result (server → client) Successful authentication. Errors are delivered as an error message instead.
Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
data required authenticated required userId required The account id the key belongs to.
ts required Server timestamp, Unix milliseconds.
pong Pong (server → client) Reply to an application-level ping.
Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
ts required Server timestamp, Unix milliseconds.
subscribed Subscribe ack (server → client) Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
channel required Private channel name.
enum: "orders" · "balance" · "positions" · "botStatus"
ts required Server timestamp, Unix milliseconds.
unsubscribed Unsubscribe ack (server → client) Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
channel Private channel name.
enum: "orders" · "balance" · "positions" · "botStatus"
ts required Server timestamp, Unix milliseconds.
update.orders Order update (server → client) Emitted on the orders channel.
Payload
op required channel required data required event required enum: "ORDER_CREATE" · "ORDER_UPDATE"
order required Trading order returned by the Public API. Contains the fields selected by orderToPublicData from the internal IOrder model. Covers spot, margin, and futures markets.
id required LEXX order identifier — the platform-generated client order id (the exchange's native numeric order id is never exposed). Format is exchange-specific: x-MTB46TF3-{uid} (binance-spot), x-MZ8FjHFm-{uid} (binance-futures), lexxO{uid} (OKX — no dashes), lexx-{uid} (Bybit). Bot orders append the run id as a final segment (e.g. x-MZ8FjHFm-{uid}_{runId}). The id is stable for the lifetime of an order, but a modify/split creates NEW orders with new ids.
exchange required Exchange account identifier (e.g., binance-spot, binance-futures, bybit-spot, bybit-futures, okx-swap).
symbol required Trading pair symbol (e.g., BTCUSDT, SOLUSDT).
side required Order direction: BUY to go long/close short, SELL to go short/close long.
enum: "BUY" · "SELL"
type required Order type (from the internal OrderType enum). MARKET = immediate execution at best price. LIMIT = execute at specified price or better. LIMIT_MAKER = post-only limit (rejected if it would take liquidity). STOP_MARKET = market order triggered at stopPrice. STOP_LIMIT = limit order triggered at stopPrice. TAKE_PROFIT_MARKET = market order at take-profit level. TAKE_PROFIT_LIMIT = limit order at take-profit level. Note: trailing stops are represented as STOP_MARKET plus a populated trailing object, not a distinct order type.
enum: "MARKET" · "LIMIT" · "LIMIT_MAKER" · "STOP_MARKET" · "STOP_LIMIT" · "TAKE_PROFIT_MARKET" · "TAKE_PROFIT_LIMIT"
status required Order lifecycle state. NEW = accepted but not yet live on exchange. OPENED = active on exchange. CLOSED = fully filled. CANCELLED = cancelled. ERROR = rejected.
enum: "NEW" · "OPENED" · "CLOSED" · "CANCELLED" · "ERROR"
positionSide Position direction for hedge-mode futures. BOTH = one-way mode.
enum: "LONG" · "SHORT" · "BOTH"
price Limit price. String-encoded decimal.
stopPrice Trigger price for stop/take-profit orders.
fillPrice Volume-weighted average fill price. Null if no fills yet.
origQty Originally requested quantity in base asset.
executedQty Quantity already filled.
reduceOnly If true, order can only reduce an existing position.
closePosition If true, closes the entire position.
trailing Trailing stop parameters. Present (populated) when the order is a trailing stop; the type field remains STOP_MARKET in that case.
delta Callback rate in basis points (100 = 1%).
time Activation timestamp (Unix ms).
isContract True for futures/perpetual contracts, false for spot.
creatorId Identifier of the entity that created this order (bot ID, user short ID).
creator Source that created this order.
enum: "API" · "UI" · "BOT"
isWorking True if the order is currently active on the exchange order book.
metadata JSON-encoded metadata string. Contains allocation info for bot orders, creation timestamps, etc. Parse with JSON.parse().
specific Exchange-specific fields (e.g., goodTillDate). Structure varies by exchange.
orderTime required Timestamp when the order was submitted, in Unix milliseconds.
ts required Server timestamp, Unix milliseconds.
update.balance Balance update (server → client) Emitted on the balance channel.
Payload
op required channel required data required event required exchange required marketType changes required Changed balance entries keyed by ASSET ticker, plus an optional total wallet summary. Each asset entry is {free, wallet, locked, lockedByBot?, usd?} — all string-encoded decimals. total additionally carries ts (Unix ms) and, on futures, mmr, imr, upnl, crossUpnl, crossWallet. Spot and futures share the same shape — distinguish them by marketType on the envelope.
total Aggregated wallet summary (optional).
ts required Server timestamp, Unix milliseconds.
update.positions Position update (server → client) Emitted on the positions channel. changes is a batch of positions that changed, keyed by symbol.
Payload
op required channel required data required event required exchange required changes required Changed positions keyed by symbol. Each value is a PARTIAL position object — only the fields that changed are present. Fields mirror the REST PublicPosition model: symbol, quote, marketType, amount, entryPrice, unrealizedProfit, type (ISOLATED/CROSSED), leverage, side, isolatedWallet, liquidationPrice, bepPrice, mmr (string-encoded decimals) and updateTime (Unix ms number). Futures only — unlike balance, this envelope has no marketType field.
ts required Server timestamp, Unix milliseconds.
update.botStatus Bot status update (server → client) Emitted on the botStatus channel.
Payload
op required channel required data required event required enum: "BOT_STARTED" · "BOT_STOPPED" · "BOT_ERROR" · "BOT_UPDATE"
bot required Sanitized bot record — the internal bot entry with internal-only fields removed (userId, apiKeyId, keyId, apiKey, secret, credentials, workerId, worker, internalConfig, _id, __v are never present; same deny-list as the REST PublicBot). It is an OPEN object: beyond the fields below it may carry additional lifecycle fields (e.g. runId). Inlined here because PublicBot uses an OpenAPI-style discriminated union that is not valid in the AsyncAPI schema context.
id type enum: "FIBO" · "CHANNEL" · "SQUEEZE" · "INTRADAY" · "ALGO" · "ALARM" · "NOTIFY"
status exchange symbol ts required Server timestamp, Unix milliseconds.
error Error (server → client) Any protocol/auth/subscription error. Some errors are followed by a socket close (see close codes).
Payload
op required id Optional client-chosen correlation id; echoed back on the matching reply.
data required code required Machine-readable error code.
enum: "AUTH_REQUIRED" · "AUTH_TIMEOUT" · "AUTH_IN_PROGRESS" · "CHALLENGE_MISSING" · "TIMESTAMP_INVALID" · "TIMESTAMP_EXPIRED" · "INVALID_ARGS" · "INVALID_KEY" · "INVALID_SIGNATURE" · "UNAUTHORIZED" · "IP_REJECTED" · "INSUFFICIENT_PERMISSIONS" · "TOO_MANY_ATTEMPTS" · "KEY_REVOKED" · "RATE_LIMITED" · "INVALID_JSON" · "INTERNAL_ERROR"
message required ts required Server timestamp, Unix milliseconds.