Skip to main content
WSS wss://ws-gateway.dev.predictstreet.sde.adifoundation.ai/ws/user
Per-partner activity gateway. Subscribe once with your API key and you’ll receive live events for every order and trade placed by the wallet associated with the key. Trying to subscribe to a public channel (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
The handshake rejects missing, malformed, revoked, expired, suspended, or IP-denied keys with a 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.
Auth is validated once at handshake. Rotating the key does not invalidate an open socket — close and reopen to switch identity. Revoking a key on the admin panel closes every live socket bound to that keyId immediately via Redis apikey:invalidate pub/sub.

Close codes

Close codeMeaning
4401 api_key_bad_format / api_key_unknown_key / api_key_bad_secretMalformed token or the server doesn’t recognise the keyId / secret pair. Rotate the key on the admin panel.
4401 api_key_revoked / api_key_expiredKey was explicitly killed or reached its expiry. Issue a fresh one.
4401 api_key_suspendedThe partner itself is SUSPENDED in admin. Contact ops — do not auto-retry.
4401 api_key_ip_deniedCaller IP isn’t in the key’s allowlist.
4401 api_key_auth_disabled / api_key_auth_unconfiguredServer-side misconfig (feature flag off or pepper missing). Ops issue, not the caller’s.
1008 forbidden originWebSocket Origin header not in the allowlist.

Connect greeting

The server pushes a single connected frame after the upgrade handshake so you don’t need a parallel REST call to learn the wallet tied to the key:
{
  "type": "connected",
  "data": {
    "gateway":       "user",
    "walletAddress": "0xb27d13d9bc68e08249146f3e5f17bc08c77c66ce",
    "authMethod":    "api_key"
  }
}

Available channels

Channelids shapeRequired scopeServer pushes
user_activitynone (always scoped to the key’s associated wallet)portfolio:readorder_placed, order_cancelled, trade_matched, trade_fill, trade_failed, vault.deposit_confirmed, vault.deploy_confirmed, user_paused, user_unpaused
vault_activityvault address[] (1+ required)portfolio:readtrade_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).
See Server events for full payload shapes per type.

Subscribe example

import WebSocket from 'ws';

const KEY = process.env.PREDICTSTREET_API_KEY; // ps_live_<keyId>_<secret>
const ws = new WebSocket(
  'wss://ws-gateway.dev.predictstreet.sde.adifoundation.ai/ws/user',
  { headers: { 'X-Api-Key': KEY } },
);

ws.on('open', () => {
  ws.send(JSON.stringify({
    id: 1,
    cmd: 'subscribe',
    params: { subscriptions: [{ channel: 'user_activity' }] },
  }));
});

ws.on('close', (code, reason) => {
  if (code === 4401) console.error('auth rejected:', reason.toString());
});

Subscribe response

{
  "id": 1,
  "type": "subscribed",
  "accepted": [
    { "sid": 20, "channel": "user_activity" }
  ],
  "rejected": []
}

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.
{
  "type": "trade_matched",
  "sid":  20,
  "channel": "user_activity",
  "id":   "0xb27d13d9bc68e08249146f3e5f17bc08c77c66ce",
  "data": { "price": "0.41", "tradeId": "...", ... }
}

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.
{
  "type": "trade_fill",
  "sid":  20,
  "channel": "user_activity",
  "id":   "0xb27d13d9bc68e08249146f3e5f17bc08c77c66ce",
  "data": {
    "side":        "taker",
    "orderHash":   "0x84a930f9e902ae91...",
    "makerVault":  "0xeBFb558d...",
    "takerVault":  "0xb27d13d9...",
    "makerAssetId": "0",
    "takerAssetId": "9871...",
    "makerAmount": "410000",
    "takerAmount": "1000000",
    "fee":         "2050",
    "blockNumber": "1234567",
    "txHash":      "0xc01780b2c76494d8..."
  }
}

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.
{
  "type": "trade_failed",
  "sid": 20,
  "channel": "user_activity",
  "data": {
    "orderId":    "0x84a930f9e902ae91...",
    "reason":     "0xc56873ba",
    "reasonText": "OrderExpired()",
    "txHash":     "0xc01780b2c76494d8..."
  }
}

Rejection codes

codeWhen
invalid_paramsunknown channel, malformed id, missing required ids, or user_activity sent with ids
api_key_scope_missingkey is missing the portfolio:read scope required by user_activity
forbiddentried a public channel on this gateway (use /ws/market)

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.