Game Launch Flow
This page describes the flow from the moment a player clicks a game on the operator site, through the game screen opening and the player leaving, to the end of the session. For request and response field details, see POST /launch.
Overall sequence
Player browser Operator server Platform
│ ① Click a game │ │
│ (open blank window │ │
│ first) │ │
│ ── Launch request ─────▶ │ │
│ │ ② POST /operator/v1/launch │
│ │ ───────────────────────────▶ │ Create / update player
│ │ ◀─────────────────────────── │ launch_url (60s, single use)
│ ◀─── launch_url ──────── │ │
│ ③ Navigate new window (or iframe) to launch_url │
│ ──────────────────────────────────────────────────────▶ │ Token exchange → platform session
│ ◀─── postMessage game.loaded ────────────────────────── │
│ │ ④ Lobby / gameplay
│ │ ◀──── Wallet callbacks (balance, debit, credit, rollback)
│ ◀─── postMessage balance.changed ────────────────────── │
│ ⑤ Exit → postMessage game.closed, window closes │- When the player clicks a game or table on the site, the browser asks the operator backend to launch it.
- The operator server checks login status, block status, and so on, then calls
POST /operator/v1/launchwith a signature and receives alaunch_url. - The browser sends the new window (or iframe) to the
launch_url. The game screen immediately exchanges the token in the URL to create a platform session and enters the lobby or the specified game or table. - While the game is in progress, the platform sends callbacks to the operator wallet, and notifies the site window via postMessage when the balance changes.
- When the player exits, the game screen sends
game.closedand closes the window.
Credentials stay on the server
Always call POST /launch from the operator server. Calling the Operator API directly from the browser exposes the API secret.
Launch request
The main fields of the request sent by the operator server. The full list of fields and error codes is in POST /launch.
{
"player": {
"external_id": "user_1001",
"nickname": "Lucky7",
"currency": "PHP",
"country": "PH"
},
"game_code": "live_holdem",
"table_id": "tbl_0192f3a1c4e87b2c9d0e1f2a3b4c5d70",
"lang": "en",
"device": "mobile",
"return_url": "https://www.acme.example/",
"operator_session_id": "c2f1e8a09b7d4e36"
}| Field | Behavior |
|---|---|
player.external_id | The operator's player ID. If the value has not been seen before, a new platform player is created. |
player.nickname | The name shown on the game screen (2–16 characters). Updated to this value on every launch. |
player.currency | The player's currency. Fixed to the currency of the first launch; launching later with a different currency returns 409 CURRENCY_MISMATCH. |
game_code / table_id | If both are omitted, the full lobby opens; with only game_code, that game's lobby opens; with table_id as well, the player goes directly to that table. |
lang | The game screen language. See Language below |
device | desktop (default) or mobile. Used to select the live video quality. |
return_url | The address to go to when "Back" is pressed on the game screen, and also the postMessage target origin. If empty, the registered default return URL is used. |
operator_session_id | A session identifier set by the operator. It is passed through unchanged in the wallet callback bodies of the session created by this launch. |
Use an opaque ID for operator_session_id
This value is kept in the platform's session records and in wallet callback bodies. Instead of the raw site login cookie or authentication token, use a separate random ID that refers to the session.
Response:
{
"launch_url": "{GAME_ORIGIN}/launch#token=lt_9c1e0b7d2f4a4c6e8a0b1c2d3e4f5a6b",
"expires_at": "2026-10-19T03:21:07.512Z",
"player_id": "pl_0192f3a1c4e87b2c9d0e1f2a3b4c5d6e"
}launch_url and token
| Item | Details |
|---|---|
| Format | {GAME_ORIGIN}/launch#token=lt_… |
| Validity | 60 seconds after issuance (expires_at) |
| Number of uses | Once. The token is consumed as soon as it is opened. |
| Location | URL fragment (after #). Browsers do not send it to the server, so it does not appear in web server logs or the Referer header, and the game screen removes it from the address bar as soon as it reads it. |
- Use the
launch_urlexactly as received. Do not append a query or change the fragment. - Issue a new one every time the player clicks. If you create one in advance or cache it, it becomes unusable after 60 seconds.
- Opening an already used or expired token shows an expiration notice and a Back button on the game screen.
- The token exchange takes place between the platform and the player's browser. The operator does not need to handle the platform session token.
Opening the window
New window (recommended)
To avoid browser popup blockers, open a blank window synchronously inside the click handler first, then navigate that window after receiving the launch_url from the server.
async function openGame(gameCode, tableId) {
// 1) Open it right inside the click event (opening after an await triggers the popup blocker)
const mobile = matchMedia('(max-width: 768px)').matches;
const features = mobile
? undefined // new tab on mobile
: `popup,left=${screen.availLeft ?? 0},top=${screen.availTop ?? 0},width=${screen.availWidth},height=${screen.availHeight}`;
const win = window.open('about:blank', `game_${gameCode}`, features); // do not pass noopener
// 2) The operator backend calls POST /operator/v1/launch and returns the launch_url
const res = await fetch('/api/launch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ game_code: gameCode, table_id: tableId, device: mobile ? 'mobile' : 'desktop' }),
});
if (!res.ok) {
win?.close();
return; // show an error message
}
const { launch_url } = await res.json();
// 3) Send the blank window to the game
if (win) win.location.href = launch_url;
else location.href = launch_url; // if the popup was blocked, use the current window
}- Do not use
noopenerornoreferrer. The game screen sends postMessage messages to the site window viawindow.opener, and on mobile it switches to fullscreen on the first touch when an opener exists. Without an opener, no events arrive. - If you name the window per game (
game_<game_code>), clicking the same game again reuses the existing window instead of opening another one. - It is a good idea to periodically check whether the window has closed (
win.closed) and refresh the balance shown on the site.
iframe
You can also embed the game in an iframe within your site.
<iframe src="LAUNCH_URL" allow="autoplay; fullscreen" style="border:0;width:100%;height:100%"></iframe>- The site domains that will embed the iframe must be registered as embed domains (
embed_origins) during onboarding. The game screen opens in an iframe only within registered domains (CSPframe-ancestors); on unregistered domains, the browser blocks it from displaying. - In an iframe, the game screen sends postMessage messages via
window.parent. - In an iframe, the game screen does not close or navigate itself. When you receive
game.closed, the operator must close or hide the iframe. - Automatic mobile fullscreen is not performed inside an iframe.
- Set
return_urlto the origin of the page that embeds the iframe. The postMessage target origin is determined by this value.
Exit and return_url
The game screen's exit ("Back to site") behaves as follows. In every case, game.closed is sent first.
| How the game screen was opened | Behavior |
|---|---|
| iframe | Only sends game.closed. The operator cleans up the iframe. |
| New window (with opener) | Closes the window. |
| Window or tab without an opener | Navigates to return_url. |
The "Back" button on the session expired screen and the already-used token screen also goes to return_url. If there is neither a return_url nor a default return URL, no postMessage events are sent at all, so be sure to set one of them.
postMessage events
The game screen sends the events below to the site window (window.opener for a new window, window.parent for an iframe).
- Message shape:
{ source: "games-platform", type, payload } - Target origin (
targetOrigin): the origin ofreturn_url(the launch request value, or the registered default return URL if absent). If the site window's origin differs from this, the browser does not deliver the message. - Messages in the site → game screen direction are not accepted.
type | Sent when | payload |
|---|---|---|
game.loaded | The token exchange completes and the game screen starts | { "game_code": "live_holdem" } — null if no game was specified |
balance.changed | The player's balance changes due to a wallet transaction | { "balance": 1250000, "currency": "PHP" } — amount is an integer in the minor unit |
game.closed | The player presses exit | { "return_url": "https://www.acme.example/" } |
session.expired | The session expires or ends and the screen switches to the expired screen | {} |
Receiver example:
const GAME_ORIGIN = '{GAME_ORIGIN}'; // replace with the game screen origin issued for your environment
window.addEventListener('message', (e) => {
if (e.origin !== GAME_ORIGIN) return; // check the origin
const m = e.data;
if (!m || m.source !== 'games-platform') return; // check the source marker
switch (m.type) {
case 'balance.changed':
showBalance(m.payload.balance, m.payload.currency);
break;
case 'game.closed':
case 'session.expired':
refreshBalanceFromServer(); // re-query based on the wallet ledger
closeGameFrameIfAny();
break;
}
});- Always compare
event.originwith the game screen origin (Environment addresses). balance.changedis for display only. The source of truth for the balance is the operator wallet, so do not use it for settlement or withdrawal decisions.
Language
| Item | Details |
|---|---|
| Supported languages | ko en zh ja th vi |
| Format | Values with mixed region or case, such as zh-CN, ja_JP, or EN, are matched by their leading part. |
| Default | ko if omitted or unsupported |
| In-game change | Players can change it with the language button on the game screen. |
Currency
- The player's currency is set by
player.currencyand must be one of the currencies registered for the operator (otherwise400 CURRENCY_NOT_SUPPORTED). - A player's (
external_id) currency cannot be changed. If your site uses multiple currencies, use a differentexternal_idfor each currency. - Players see only tables that operate in their own currency.
Session
| Item | Details |
|---|---|
| When created | One session each time a launch_url is opened and its token exchanged |
| Maximum lifetime | 12 hours. After that, the expired screen appears and session.expired is sent. To continue, a new launch is required. |
| Keep-alive | The game screen refreshes it automatically. The operator does not need to extend it. |
| Force logout | POST /players/{external_id}/logout — terminates all of that player's sessions and stands them up from any table they were seated at (cash-out after the hand in progress ends). With block: true, subsequent launches are also blocked. |
Multiple windows and devices
- Each launch creates an independent session, and previous sessions remain active. Logging out of the site does not automatically end game sessions, so call
POST /players/{external_id}/logoutas well on site logout, self-exclusion, and account suspension. - When the multiple games setting (
multi_table, off by default) is off, a player can be in only one table at a time across all windows and devices. If the player is already in another table, entering a new table is rejected (MULTI_TABLE). Change the setting withPUT /settings. - At live tables, a player who only watches without sitting or betting for longer than the configured time (
watch_limit_sec) is returned to the lobby. The setting is inPUT /settings.
Blocked players
- For a player blocked with
block: true,POST /launchis rejected with403 PLAYER_BLOCKED. - Even a
launch_urlissued before the block is rejected at the exchange step, and the expired screen appears. - To lift the block, call again with
block: false(this also terminates any open sessions).