The WebSocket API gives your app a live channel from Orenda: instead of polling REST endpoints, you hold one connection open and events are pushed as they happen. Today it carries 3DS challenges — a card payment needing approval reaches the customer’s device instantly. See 3DS events.

Connecting

wss://ws.next.orenda.finance?token=<id_token>
Pass the user’s id_token (from sign-in — the same token set as your REST calls) as the token query parameter. It’s checked once, during the handshake:
  • Valid token → connection opens (onopen).
  • Invalid or expired token → handshake rejected (onclose / onerror).
The connection is scoped to the authenticated user — you only receive their events.
The id_token is short-lived (about 5 minutes, like the access token), so by the time you reconnect it has almost certainly expired. The open connection isn’t affected (auth happens only at the handshake), but refresh before every reconnect — the refresh response includes a fresh id_token. Never reuse a cached one. And once the refresh token itself expires (about an hour), the user signs in again before the WebSocket can reconnect.

Keeping the connection alive

Two server-side limits shape your client:
LimitWhat to do
10 minutes idle — the connection drops if nothing is sentSend a ping at least every ~9 minutes: {"action": "ping"}. No reply is sent; it just keeps the connection (and your server-side registration) alive.
2 hours maximum — every connection is closed after 2 hours, active or notTreat onclose as routine: reconnect with exponential backoff (2s, 4s, 8s, … capped at 30s) and a fresh token.
A client that never pings will be disconnected after 10 idle minutes and silently miss events until it reconnects. Build the ping loop and the reconnect handler first — they’re not optional hardening.

Missed events

If a 3DS challenge is pushed while the customer is offline, it’s re-delivered automatically on reconnect — any still-pending challenge from the last 10 minutes that wasn’t delivered is pushed again as soon as the connection opens. Anything older has expired anyway (3DS challenges are short-lived). Other event types are not replayed; treat the connection as a live feed, with REST as the source of truth.

Receiving events

Every message is JSON with a code discriminator:
{
  "code": "3DS_CHALLENGE_INITIATED",
  "message": "3DS authentication required. Please verify your transaction.",
  "data": { "...": "..." }
}
Route on code (a small pub/sub registry per event type works well) and ignore codes you don’t recognise — new event types may be added without notice.