token_trades, token_book, condition_status, system)
on this gateway returns { "code": "forbidden" } in rejected[] —
use /ws/market for public streams.
Authentication
| How to send it |
|---|
X-Api-Key: ps_live_<keyId>_<secret> header on the upgrade request |
?key=<token> query parameter — for browser clients that can’t set custom headers on the WebSocket upgrade |
4401 <reason> close frame before any
subscribe command is accepted. There is no fallback to any other auth
scheme; a bad key ends the connection cleanly.
user_activity requires the portfolio:read scope on the API
key. Subscribing without it surfaces
{ "code": "api_key_scope_missing", "message": "api_key_scope_missing: channel user_activity needs portfolio:read" }
in rejected[]. See API keys for the full scope
catalog.keyId immediately via Redis apikey:invalidate pub/sub.
Close codes
| Close code | Meaning |
|---|---|
4401 api_key_bad_format / api_key_unknown_key / api_key_bad_secret | Malformed token or the server doesn’t recognise the keyId / secret pair. Rotate the key on the admin panel. |
4401 api_key_revoked / api_key_expired | Key was explicitly killed or reached its expiry. Issue a fresh one. |
4401 api_key_suspended | The partner itself is SUSPENDED in admin. Contact ops — do not auto-retry. |
4401 api_key_ip_denied | Caller IP isn’t in the key’s allowlist. |
4401 api_key_auth_disabled / api_key_auth_unconfigured | Server-side misconfig (feature flag off or pepper missing). Ops issue, not the caller’s. |
1008 forbidden origin | WebSocket Origin header not in the allowlist. |
Connect greeting
The server pushes a singleconnected frame after the upgrade
handshake so you don’t need a parallel REST call to learn the wallet
tied to the key:
Available channels
| Channel | ids shape | Required scope | Server pushes |
|---|---|---|---|
user_activity | none (always scoped to the key’s associated wallet) | portfolio:read | order_placed, order_cancelled, trade_matched, trade_fill, trade_failed, vault.deposit_confirmed, vault.deploy_confirmed, user_paused, user_unpaused |
vault_activity | vault address[] (1+ required) | portfolio:read | trade_fill (per vault — id on the push is the lower-cased vault address). Use when one key tracks many vaults (multi_wallet partners, custodial brokers). |
type.
Subscribe example
Subscribe response
Push examples
trade_matched
Fires immediately at off-chain match — the trade row is now matched
in our DB but not yet on-chain confirmed. Use this to update your
order book / open-trades panel; do NOT treat this as a balance commit.
trade_fill
Fires when chain-watcher confirms the on-chain OrderFilled event for
your vault — this is the settlement-confirmed signal. Use this for
balance reconciliation: after this fires, your available / locked
totals reflect the trade. The payload carries the txHash,
blockNumber, vault address, and matched amounts; side is
'maker' or 'taker' from your perspective.
trade_failed
Fires when a settlement leg reverts on-chain (MatchFailed event).
After this, the matched balance lock is refunded; treat the trade as
never having settled.
Rejection codes
code | When |
|---|---|
invalid_params | unknown channel, malformed id, missing required ids, or user_activity sent with ids |
api_key_scope_missing | key is missing the portfolio:read scope required by user_activity |
forbidden | tried a public channel on this gateway (use /ws/market) |
Related
Commands
update_subscription / unsubscribe / list_subscriptions / ping.
Server events
Full event payload catalog per channel.
Reconnect
Heartbeat, sid rebuild, snapshot resync.
API keys
Scope catalog, rotation, revoke.