Skip to content

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        │
  1. When the player clicks a game or table on the site, the browser asks the operator backend to launch it.
  2. The operator server checks login status, block status, and so on, then calls POST /operator/v1/launch with a signature and receives a launch_url.
  3. 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.
  4. 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.
  5. When the player exits, the game screen sends game.closed and 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.

json
{
  "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"
}
FieldBehavior
player.external_idThe operator's player ID. If the value has not been seen before, a new platform player is created.
player.nicknameThe name shown on the game screen (2–16 characters). Updated to this value on every launch.
player.currencyThe player's currency. Fixed to the currency of the first launch; launching later with a different currency returns 409 CURRENCY_MISMATCH.
game_code / table_idIf 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.
langThe game screen language. See Language below
devicedesktop (default) or mobile. Used to select the live video quality.
return_urlThe 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_idA 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:

json
{
  "launch_url": "{GAME_ORIGIN}/launch#token=lt_9c1e0b7d2f4a4c6e8a0b1c2d3e4f5a6b",
  "expires_at": "2026-10-19T03:21:07.512Z",
  "player_id": "pl_0192f3a1c4e87b2c9d0e1f2a3b4c5d6e"
}

launch_url and token ​

ItemDetails
Format{GAME_ORIGIN}/launch#token=lt_…
Validity60 seconds after issuance (expires_at)
Number of usesOnce. The token is consumed as soon as it is opened.
LocationURL 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_url exactly 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 ​

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.

js
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 noopener or noreferrer. The game screen sends postMessage messages to the site window via window.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.

html
<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 (CSP frame-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_url to 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 openedBehavior
iframeOnly sends game.closed. The operator cleans up the iframe.
New window (with opener)Closes the window.
Window or tab without an openerNavigates 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 of return_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.
typeSent whenpayload
game.loadedThe token exchange completes and the game screen starts{ "game_code": "live_holdem" } — null if no game was specified
balance.changedThe player's balance changes due to a wallet transaction{ "balance": 1250000, "currency": "PHP" } — amount is an integer in the minor unit
game.closedThe player presses exit{ "return_url": "https://www.acme.example/" }
session.expiredThe session expires or ends and the screen switches to the expired screen{}

Receiver example:

js
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.origin with the game screen origin (Environment addresses).
  • balance.changed is 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 ​

ItemDetails
Supported languagesko en zh ja th vi
FormatValues with mixed region or case, such as zh-CN, ja_JP, or EN, are matched by their leading part.
Defaultko if omitted or unsupported
In-game changePlayers can change it with the language button on the game screen.

Currency ​

  • The player's currency is set by player.currency and must be one of the currencies registered for the operator (otherwise 400 CURRENCY_NOT_SUPPORTED).
  • A player's (external_id) currency cannot be changed. If your site uses multiple currencies, use a different external_id for each currency.
  • Players see only tables that operate in their own currency.

Session ​

ItemDetails
When createdOne session each time a launch_url is opened and its token exchanged
Maximum lifetime12 hours. After that, the expired screen appears and session.expired is sent. To continue, a new launch is required.
Keep-aliveThe game screen refreshes it automatically. The operator does not need to extend it.
Force logoutPOST /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}/logout as 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 with PUT /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 in PUT /settings.

Blocked players ​

  • For a player blocked with block: true, POST /launch is rejected with 403 PLAYER_BLOCKED.
  • Even a launch_url issued 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).

Docs v1.0.0 · Operator API v1