Launch and sessions
A player reaches the game through the platform launcher, which hands the game a sessionId. Every subsequent call is made against that id.
Launch flow
Launch URL
The casino's lobby sends the player's browser to the platform launcher with an encrypted token that only the casino and the platform can produce, plus the game, currency and language.
GET {LAUNCHER_URL}/launch?token=<encrypted casino token>&providerId=66ffba1337e994c3009ebf40&gameId=101¤cy=USD&language=en&backtoHome=https://casino.example/lobby # optionalSession creation
The launcher decrypts and checks the token, confirms the game is registered and active, and stores a session for one day. No casino wallet is called at this point, so a wallet outage cannot prevent a player from entering the game.
Redirect to the frontendUrl
The redirect target is always the registered
frontendUrl. Query parameters are merged as follows:sessionIdis added, always in the clear.tokenis dropped. It never reaches the frontend or the browser history.- Every other launch parameter is forwarded verbatim:
providerId,gameId,currency,language,backtoHome, and anything else the casino added. - Query parameters that are part of the registered
frontendUrlare kept and win any name clash. A launch URL cannot reconfigure a registered game.
302 Location: https://game.acme.example/play?sessionId=6f1e0d5c-2a0f-4d5e-9b47-6a1c3d2e8f10&providerId=66ffba1337e994c3009ebf40&gameId=101¤cy=USD&language=en&backtoHome=https%3A%2F%2Fcasino.example%2FlobbyForwarded parameters are untrusted input.They originate from the casino's URL and can be edited by the player. They are suitable for presentation only; the authoritative game, currency and player come from session validation in step 5.
backtoHomein particular must only be used as a link target, never as a redirect performed by the game backend.Hand-off to the game backend
The frontend passes the
sessionIdto the game backend, which holds the game provider token and makes the relay calls. The game provider token must not be present in the frontend.Session validation
The game backend calls
GET /api/v1/sessions/{sessionId}with the expectedgameId. A200means the session exists, was opened for that game and currency, the game belongs to the game provider and is active, and the casino wallet has just confirmed the player's token with a live balance call. A200here guarantees the first bet will not fail for a session reason.curl "{RELAY_BASE_URL}/api/v1/sessions/6f1e0d5c-2a0f-4d5e-9b47-6a1c3d2e8f10?gameId=101¤cy=USD" \-H "Authorization: Bearer $RELAY_TOKEN"{"statusCode": 200,"message": "Session is valid","data": {"valid": true,"sessionId": "6f1e0d5c-2a0f-4d5e-9b47-6a1c3d2e8f10","gameId": 101,"gameName": "Mines","groupId": "66ffc0a1e2b3c4d5e6f70123","providerId": "66ffba1337e994c3009ebf40","playerId": "48213","currency": "USD","language": "en","expiresAt": "2026-09-22T09:14:03.000Z"}}providerId,playerId,currencyandlanguagefrom this response are the values the game should rely on. Theusernamefield is absent at launch time; the wallet returns a display name on the first balance call.
No endpoint returns the casino token stored on the session. The game only ever handles sessionId.
Session lifetime
- A session lives for one day from launch. The
expiresAtvalue gives the instant. - The relay never extends a session. Activity does not refresh it.
- After expiry every session-scoped call answers
403withSESSION_EXPIRED. A session that never existed yields the same answer. The player must be relaunched by the casino; a game cannot create a session. - One exception:
POST /api/balance/cash-in/retrycan still resolve a session the relay has archived, for up to two months, so a delayed credit for a finished round is not lost. See credit retry.
Validation errors
| Status | Code | Meaning and handling |
|---|---|---|
| 400 | SESSION_GAME_MISMATCH | The session was opened for another game. The gameId passed from the frontend is wrong. The flow must stop. |
| 400 | SESSION_CURRENCY_MISMATCH | The currency sent is not the session's. Omit it, or send the session's currency. |
| 403 | SESSION_EXPIRED | Unknown or expired session. The game shows a "session ended, return to the casino" screen. |
| 403 | GAME_NOT_OWNED_BY_GROUP | The game exists but is registered to another game provider. The token in use is not the right one. |
| 403 | GAME_NOT_ACTIVE | The platform has disabled the game. Platform team to be contacted. |
| 404 | GAME_NOT_FOUND | The session references a game id that is not registered. Platform team to be contacted. |
| 403 / 404 | PROVIDER_NOT_ACTIVE / PROVIDER_NOT_FOUND | The casino has been disabled or removed on the platform side. Not recoverable by the game. |
| 403 | AUTHENTICATION_TOKEN_EXPIRED | The casino rejected the player's wallet token. Handled like an expired session. |
| 408 | INTEGRATION_TIMED_OUT | The casino did not answer in time. One retry of the validation is reasonable; a persistent timeout is a temporary error. |
| 502 | PROVIDER_API_FAILURE / INVALID_PROVIDER_RESPONSE | The casino answered outside the contract. Temporary error on the casino side. |
Encrypted launch parameters
When a game provider publishes an RSA public key, the launcher stops forwarding launch parameters in the clear. The redirect then takes this form:
302 Location: https://game.acme.example/play
?sessionId=6f1e0d5c-…
&payload=eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwiY3R5Ijoi….<key>.<iv>.<ciphertext>.<tag>
sessionIdstays in the clear, so a game without a backend of its own can still call the relay.- Query parameters that are part of the registered
frontendUrlstay in the clear; they are the game's own configuration. payloadis a standard JWE in compact serialization: key wrappingRSA-OAEP-256, content encryptionA256GCM. The decrypted plaintext is a URL query string, for exampleproviderId=…&gameId=101¤cy=USD&language=en. Thectyheader declares this.- When there is nothing to forward,
payloadis omitted.
Any JOSE library decrypts it. With Node and the jose package:
import { compactDecrypt, importPKCS8 } from 'jose';
const privateKey = await importPKCS8(process.env.LAUNCH_PRIVATE_KEY_PEM, 'RSA-OAEP-256');
const { plaintext } = await compactDecrypt(payload, privateKey);
const params = new URLSearchParams(new TextDecoder().decode(plaintext));
params.get('currency'); // "USD"
The public key is public, so any party can produce a payload the game will decrypt. The feature keeps launch parameters out of browser history, Referer headers and frontend logs. It does not prove the launcher produced them. Session validation remains the source of truth.
To opt in, the game provider sends the platform team its public key as a PEM block (-----BEGIN PUBLIC KEY-----). The key must be RSA, 2048 bits or longer. A private key must never be sent; the relay rejects any PEM containing the words PRIVATE KEY. The key can be rotated at any time by supplying a new one.