Real-time

User Notifications

Reconnect an authenticated WebSocket and seed balances, positions, and orders from REST

Authenticate the socket to receive updates for the signed-in user. Message shapes are in WebSocket Protocol.

No initial state is sent when the socket authenticates. Seed balances, positions, and orders from GET /execution/balances, GET /execution/positions, and GET /execution/orders, then apply socket updates. See Portfolio and Order lifecycle.

A fill is the socket event order_event with event filled or partial_fill, and the webhook event trades.filled. See Webhooks.

Refresh-aware reconnect

The socket's user session lasts as long as the access token. The gateway sends auth_expiring about a minute before expiry, and auth_expired when the session ends. Refresh the token and send authenticate again. When you reconnect, pass the last eventId you processed as resumeFrom, and events sent while you were away are replayed. Seed from REST only when the resumed frame says complete: false.

let accessToken = initialAccessToken;
let lastEventId: string | undefined;
const seen = new Set<string>();

function connect(attempt = 0) {
  const ws = new WebSocket(`wss://ws.t.sufwankhalid.com/ws?appId=${appId}`);

  const authenticate = () =>
    ws.send(JSON.stringify({ action: "authenticate", token: accessToken, resumeFrom: lastEventId }));

  ws.onmessage = async (frame) => {
    const msg = JSON.parse(frame.data);
    switch (msg.type) {
      case "connected":
        attempt = 0;
        authenticate();
        return;
      case "auth_expiring":
      case "auth_expired":
        try {
          accessToken = await client.refreshAccessToken();
          authenticate();
        } catch {
          await client.signOut();
          ws.close();
        }
        return;
      case "resumed":
        if (!msg.complete) await seedFromRest(); // some events may be missing
        return;
    }
    if (msg.eventId) {
      if (seen.has(msg.eventId)) return; // a resume can resend events you have
      seen.add(msg.eventId);
      lastEventId = msg.eventId;
    }
    applyUserEvent(msg);
  };

  ws.onclose = (event) => {
    if (event.code === 4001 || event.code === 4003) return; // configuration error
    const delay = Math.min(1000 * 2 ** attempt, 30_000) * (0.5 + Math.random() / 2);
    setTimeout(() => connect(attempt + 1), delay);
  };
}

await seedFromRest();
connect();

The SDK does all of this for you: client.createWebSocket() authenticates by message, refreshes on auth_expiring, resumes from the last eventId, de-duplicates, and reports onResumed.

Server-side: every user's events

A backend can receive all users' events for the app on one API-key connection instead of one socket per user. See App events.

On this page