Appearance
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
| Environment | Transport |
|---|---|
sandbox | In-process emitter inside the SDK (identical listener APIs, no network) |
staging / production | One 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 module → type pairs (module pages are the detail source; this is the index):
| Module | Closed type union | Payload defined in |
|---|---|---|
WALLET | PAYMENT_UPDATED | Payment Events |
GAMES | ROUND_RESOLVED | Round History |
RESPONSIBLE_GAMING | LIMIT_ACTIVATED | LIMIT_REMOVED | SELF_EXCLUSION_STARTED | SELF_EXCLUSION_EXPIRED | REALITY_CHECK_DUE | RG Overview · Self-Exclusion & Reality Checks |
MESSAGING | NOTIFICATION | Real-Time Notifications |
ENGAGEMENT | BONUS_COMPLETED | BONUS_EXPIRED | TIER_CHANGED | TOURNAMENT_COMPLETED | PRIZE_SETTLED | Engagement Overview |
SUPPORT | CHAT_MESSAGE_RECEIVED | Live Chat & Widgets |
CONTENT | CONTENT_UPDATED | Dynamic Configuration |
Event families (per module)
| Module | Events | Defined in |
|---|---|---|
| Wallet | Payment intent / withdrawal state changes | Payment Events |
| Engagement | Bonus, tier, tournament, prize events | Engagement Overview |
| Games | Round resolutions (COMPLETED or FAILED) | Round History |
| Responsible Gaming | Limit activated/removed, self-exclusion started/expired, reality-check due | Responsible Gaming |
| Messaging | Real-time notifications (also persisted to the inbox) | Real-Time Notifications |
| Support | Agent chat replies | Live Chat & Widgets |
| Content | Content/config update notifications | Dynamic 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.