Skip to content

Real-Time Channel

The platform's single real-time event channel. Every module that pushes events (wallet payment updates, engagement engine changes, game round resolutions, responsible-gaming transitions, messaging notifications, support chat replies, content updates) rides this one channel. This page is the canonical contract; module pages define only their event envelopes and fire conditions, and link here.

Overview

EnvironmentTransport
sandboxIn-process emitter inside the SDK (identical listener APIs, no network)
staging / productionOne authenticated WebSocket connection per client, multiplexing every module's events

One connection, many event families. Subscribing to a module's events never opens a second connection.

Connection lifecycle

text
GIVEN an authenticated session
WHEN the app connects the real-time channel
THEN a single connection opens and stays open, multiplexing every
     module's event subscriptions.

GIVEN no authenticated session
WHEN a connection is attempted
THEN it does not open; connect after login.
  • Reconnects are automatic: exponential backoff (1s, 2s, 4s, 8s, 16s; max 5 attempts), then the connection enters a documented give-up state surfaced through the connection-state events.
  • Connection state is observable, so your UI never has to guess whether it is live:
typescript
type ConnectionState = 'CONNECTED' | 'RECONNECTING' | 'GAVE_UP';

interface ConnectionStateEvent {
  state: ConnectionState;
  detail?: string;
}
text
GIVEN an open connection that drops
WHEN reconnecting
THEN ConnectionStateEvent { state: 'RECONNECTING' } fires for every subscriber.

GIVEN a connection that exhausts its 5 reconnect attempts
WHEN the last attempt fails
THEN ConnectionStateEvent { state: 'GAVE_UP' } fires. The SDK will not retry
     on its own; call connect again when your app regains connectivity.

Authentication

The access token never appears in a URL. Connection authentication uses a single-use ticket:

text
GIVEN an authenticated session
WHEN the channel needs to open or re-authenticate
THEN the SDK first requests a one-time ticket over authenticated HTTP
     (60-second TTL, single use) and presents ONLY that ticket in the
     WebSocket handshake; the access token itself never leaves the
     HTTP layer.

Tickets are scoped to one connection attempt; each (re)connect obtains a fresh one.

Delivery guarantees

Delivery is at-least-once, bounded by a 24-hour replay window:

text
GIVEN a client that reconnects after an absence
WHEN its last seen event is within the last 24 hours
THEN all missed events for that period are redelivered.

GIVEN a client reconnecting after more than 24 hours
WHEN it needs current state
THEN it reconciles by fetching current state via the module's APIs;
     historical events beyond 24 hours are never replayed.

Deduplicate by eventId. Every envelope carries a unique eventId; a redelivered event must be treated as one. Redelivery means events can arrive out of order; tolerate it by keying on eventId, not on arrival order.

Domain events vs transport failures

Domain events never fire for transport failures; transport failures surface typed errors. An event firing always means a real state change happened on the platform (a payment completed, a bonus expired, a round resolved). Timeouts and network errors are transport-level facts; the caller learns about them through typed errors on the call that failed, never through this channel, and no state machine advances because of one.

Envelope

Every event is a typed, closed structure carrying an eventId. The envelope is a discriminated union: the module field selects the family, and each family has its own closed type union:

typescript
type RealtimeEnvelope =
  | { eventId: string; module: 'WALLET';           type: 'PAYMENT_UPDATED';    payload: PaymentEvent;          occurredAt: string; }
  | { eventId: string; module: 'GAMES';            type: 'ROUND_RESOLVED';     payload: GameRoundResolvedPayload; occurredAt: string; }
  | { eventId: string; module: 'RESPONSIBLE_GAMING'; type: 'LIMIT_ACTIVATED' | 'LIMIT_REMOVED' | 'SELF_EXCLUSION_STARTED' | 'SELF_EXCLUSION_EXPIRED' | 'REALITY_CHECK_DUE'; payload: RgEventPayload; occurredAt: string; }
  | { eventId: string; module: 'MESSAGING';        type: 'NOTIFICATION';       payload: RealtimeMessage;       occurredAt: string; }
  | { eventId: string; module: 'ENGAGEMENT';       type: 'BONUS_COMPLETED' | 'BONUS_EXPIRED' | 'TIER_CHANGED' | 'TOURNAMENT_COMPLETED' | 'PRIZE_SETTLED'; payload: EngagementEventPayload; occurredAt: string; }
  | { eventId: string; module: 'SUPPORT';          type: 'CHAT_MESSAGE_RECEIVED'; payload: ChatMessageReceivedPayload; occurredAt: string; }
  | { eventId: string; module: 'CONTENT';           type: 'CONTENT_UPDATED';   payload: ContentUpdatedPayload;  occurredAt: string; };

The closed moduletype pairs (module pages are the detail source; this is the index):

ModuleClosed type unionPayload defined in
WALLETPAYMENT_UPDATEDPayment Events
GAMESROUND_RESOLVEDRound History
RESPONSIBLE_GAMINGLIMIT_ACTIVATED | LIMIT_REMOVED | SELF_EXCLUSION_STARTED | SELF_EXCLUSION_EXPIRED | REALITY_CHECK_DUERG Overview · Self-Exclusion & Reality Checks
MESSAGINGNOTIFICATIONReal-Time Notifications
ENGAGEMENTBONUS_COMPLETED | BONUS_EXPIRED | TIER_CHANGED | TOURNAMENT_COMPLETED | PRIZE_SETTLEDEngagement Overview
SUPPORTCHAT_MESSAGE_RECEIVEDLive Chat & Widgets
CONTENTCONTENT_UPDATEDDynamic Configuration

Event families (per module)

ModuleEventsDefined in
WalletPayment intent / withdrawal state changesPayment Events
EngagementBonus, tier, tournament, prize eventsEngagement Overview
GamesRound resolutions (COMPLETED or FAILED)Round History
Responsible GamingLimit activated/removed, self-exclusion started/expired, reality-check dueResponsible Gaming
MessagingReal-time notifications (also persisted to the inbox)Real-Time Notifications
SupportAgent chat repliesLive Chat & Widgets
ContentContent/config update notificationsDynamic Configuration

Unsubscribe semantics

Every subscription returns an unsubscribe function. Call it on component unmount; a listener that is unsubscribed receives nothing further, including replays delivered afterwards.

Sandbox behavior

Sandbox uses an in-process emitter with the same listener APIs; the only divergence is that delivery is synchronous and there is no network to drop. Sandbox controls that produce events (sdk.<module>.sandbox.*, the platform time controls) drive the same envelopes end-to-end.

Divergence flag: in sandbox, events cannot be "missed" by disconnection (there is no connection), and the 24-hour replay window does not apply.