1. Check your client configuration
Call this from your backend during setup and cache the response:
https://arcade.puntlab.solutions/api/client-config
Authorization: Bearer <api-key>
{
"data": {
"clientName": "Example production",
"allowedOrigins": [
"https://www.example.com"
],
"enabledGames": [
"elite-drop",
"elite-wheel",
"elite-deal",
"elite-mines"
],
"callback": {
"mode": "registered",
"configured": true,
"url": "https://api.example.com/elitearcade/callback",
"secretFingerprint": "sha256:1a2b3c4d",
"perLaunchOverridesAllowed": false,
"launchFields": "omit"
},
"launchContract": "config-v1",
"expiresInSeconds": {
"minimum": 60,
"maximum": 86400,
"default": 900
}
}
}
This returns your registered origins, enabled games, expiry limits, callback mode, and a short SHA-256 secret fingerprint. It never returns the callback secret.
If callback.launchFields is omit, do not send
callback fields at launch. If it is required, send
callbackUrl and callbackSecret together.
Supplied secrets must contain 16–256 characters.
Compare callback.secretFingerprint with the first eight
hex characters of your secret's SHA-256 hash before testing signatures.
2. Create a game session
Call this endpoint from your backend, never from the browser:
https://arcade.puntlab.solutions/api/launch-sessions
Authorization: Bearer <api-key>
Content-Type: application/json
{
"externalSessionId": "123456",
"gameId": "elite-drop",
"campaignId": "123456",
"allowedOrigins": [
"https://www.example.com"
],
"expiresInSeconds": 900,
"config": {
"rewardType": "dollar",
"rewardValues": [
"?",
50,
60,
40,
20,
40,
60,
"?"
],
"end-game-challenge": false,
"expectedReturn": 45
},
"promo": {
"subtitleHTML": "Drop ball to reveal your <strong>deposit bonus</strong>.",
"claimText": "CLAIM BONUS BET",
"rewardDescriptionTemplate": "You won a {value} bonus bet."
}
}
externalSessionId- Your unique ID for this eligible play. Reusing it safely returns the original session.
campaignId- Your campaign or promotion reference.
gameIdelite-drop,elite-wheel,elite-deal, orelite-mines.allowedOrigins- HTTPS origins allowed to embed this session. Every value must appear in the client configuration response.
expiresInSeconds- Session lifetime from
60to86400seconds. config.expectedReturn- The required target average whole-dollar reward used to build the game.
config.rewardValues- Optional Drop bucket or Wheel one-use boost values. Drop supports
?mystery buckets. Wheel accepts 2–12 positive whole-dollar values. Every Wheel slice is equally likely, and each landed boost becomes an equal-size Lose Boost slice. When omitted, EliteArcade calculates them. config.bankLevel- Optional for Elite Mines. Sets the guaranteed bank after the first safe pick.
config.progressionValues- Optional for Elite Mines. Positive whole-dollar increments for later safe picks, for example
[2, 2, 3, 5, 6, 11]. The ladder must produceexpectedReturnusing the configured mine count's natural odds. config.mineCount- Optional for Elite Mines. Sets 1–14 mines on the 4×4 board. The default is
2. After the guaranteed opening pick, risk is mines divided by remaining tiles. config.caseValues- Optional for Elite Deal. Supply exactly 12 whole-dollar case values. Manual values are accepted as supplied and do not need to average to
expectedReturn. Values form an unordered prize pool and are drawn naturally without replacement. config.rewardType- Optional. The supported value and default is
dollar. config.end-game-challenge- Optional. The default is
false. promo- Optional display copy.
subtitleHTMLallows plain text and<strong>only. callbackUrl- Send only when
callback.launchFieldsisrequired. Production URLs must use HTTPS and resolve publicly. callbackSecret- Send with
callbackUrlwhen required. It must be 16–256 characters and remain backend-only.
Response
{
"data": {
"sessionId": "54ef91fc-b3d2-4eb6-93a7-b2572cfab652",
"expiresAt": "2026-07-30T12:15:00.000Z",
"callbackSecretFingerprint": "sha256:1a2b3c4d",
"iframeUrl": "https://arcade.puntlab.solutions/api/game/frame/54ef91fc-b3d2-4eb6-93a7-b2572cfab652#launch=<signed-token>"
}
}
Compare callbackSecretFingerprint on your backend.
Pass only sessionId, expiresAt, and
iframeUrl to your frontend. Do not extract, log, or
rewrite the URL fragment.
Errors
INVALID_REQUEST- Fix each field in the structured
issuesarray. LAUNCH_CONFIGURATION_INVALID- Fix every game, origin, or callback issue listed; compare it with
/api/client-config. EMBED_ORIGIN_NOT_REGISTERED- Use only origins listed by
/api/client-config. CLIENT_ORIGINS_NOT_CONFIGURED- Ask EliteArcade to register at least one iframe origin for the client.
WILDCARD_ORIGIN_FORBIDDEN- Replace
*with explicit HTTPS origins. INSECURE_EMBED_ORIGIN- Use HTTPS for every production iframe origin.
CALLBACK_OVERRIDE_FORBIDDEN- A callback is already registered. Omit
callbackUrlandcallbackSecret. CALLBACK_REQUIRED- No callback is registered. Supply both callback fields together.
CALLBACK_URL_INVALID- Use a public HTTPS callback URL without embedded credentials.
GAME_DISABLED- Use one of the enabled games returned by
/api/client-config. API_KEY_INVALID- Check the backend bearer API key.
EXTERNAL_SESSION_CONFLICT- The ID was already used with different immutable settings. Retry the original payload or use a new eligible-play ID.
RATE_LIMITED/5xx- Retry with backoff using the same payload and
externalSessionId.
3. Display the iframe
<iframe
id="elitearcade-game"
title="EliteArcade game"
src="https://arcade.puntlab.solutions/api/game/frame/54ef91fc-b3d2-4eb6-93a7-b2572cfab652#launch=<signed-token>"
style="border:0;width:100%;max-width:430px;min-height:680px"
allow="autoplay"
></iframe>
The games are mobile-first at 390–430px and scale to desktop.
EliteArcade restricts the iframe to the supplied
allowedOrigins.
Iframe messages control the host UI. Signed callbacks update your
backend. Only the signed completed callback is settlement
evidence.
Event states
- Launch response
- Your backend created the session. Embed the returned
iframeUrl; this is not an iframe event or callback. elitebet:ready- Iframe message only. The game loaded and is ready to show.
elitebet:commenced- Iframe message and signed callback. The first game action was accepted; no reward exists yet.
elitebet:completed- Iframe message and signed callback. The outcome settled; grant only from the verified callback.
elitebet:done- Iframe message only. The player pressed Done; close the host modal or iframe.
elitebet:timeout- Iframe message and signed callback. The session expired before play began; request a new session to play.
elitebet:error- Iframe message only. The game could not load; show an error and close or replace the iframe.
elitebet:resize- Iframe message only. Optionally set the iframe height from
detail.height.
Normal play follows ready → commenced → completed → done.
An unplayed session follows ready → timeout. Once commenced,
that session cannot time out. These are logical UI states; signed
callbacks are delivered independently and may arrive later or be retried.
Listen for iframe messages
window.addEventListener("message", (event) => {
const frame = document.querySelector("#elitearcade-game");
if (event.origin !== "https://arcade.puntlab.solutions") return;
if (event.source !== frame.contentWindow) return;
const { type, detail } = event.data || {};
switch (type) {
case "elitebet:ready": showGame(); break;
case "elitebet:commenced": markGameStarted(); break;
case "elitebet:completed": showSettledState(); break;
case "elitebet:done": closeGameModal(); break;
case "elitebet:timeout": showExpiredMessage(); break;
case "elitebet:error": showGameError(detail?.message); break;
case "elitebet:resize":
if (Number.isFinite(detail?.height)) {
frame.style.height = Math.ceil(detail.height) + "px";
}
break;
}
});
Every lifecycle message has { type, detail } and an
eventStatus inside detail. The resize message
instead supplies detail.gameId and detail.height.
4. Verify callbacks
Your backend receives exactly three signed callback statuses:
commenced- The player started. It has no settlement value and is sent once.
completed- The canonical result. Verify it before granting the reward.
timeout- The session expired unplayed. It has no settlement value and is mutually exclusive with
commenced.
Process callbacks by eventStatus and eventId; do not depend on delivery order.
Commenced callback
{
"event": "elitebet.game_event",
"eventStatus": "commenced",
"eventId": "54ef91fc-b3d2-4eb6-93a7-b2572cfab652:commenced",
"sessionId": "54ef91fc-b3d2-4eb6-93a7-b2572cfab652",
"externalSessionId": "123456",
"campaignId": "123456",
"gameId": "elite-drop",
"resultType": null,
"outcomeId": null,
"reward": null,
"createdAt": "2026-07-30T12:04:31.120Z"
}
Timeout callback
{
"event": "elitebet.game_event",
"eventStatus": "timeout",
"eventId": "54ef91fc-b3d2-4eb6-93a7-b2572cfab652:timeout",
"sessionId": "54ef91fc-b3d2-4eb6-93a7-b2572cfab652",
"externalSessionId": "123456",
"campaignId": "123456",
"gameId": "elite-drop",
"resultType": null,
"outcomeId": null,
"reward": null,
"createdAt": "2026-07-30T12:15:00.000Z"
}
Completed callback
{
"event": "elitebet.game_result",
"eventStatus": "completed",
"eventId": "0e8cd393-391c-42e3-92d7-a92db0096e70",
"sessionId": "54ef91fc-b3d2-4eb6-93a7-b2572cfab652",
"externalSessionId": "123456",
"campaignId": "123456",
"gameId": "elite-drop",
"resultType": "drop-claim",
"outcomeId": "55d1b5f4-4177-4202-a23a-ef46ca65d7e4",
"reward": {
"kind": "bonus-bet",
"value": 45
},
"createdAt": "2026-07-30T12:05:16.927Z"
}
outcomeId is nullable. It is normally null for
deal-accept, deal-final,
wheel-cashout, mines-cashout, and a
declined endgame-final. Use the always-present
eventId for idempotency and reconciliation.
Signature headers
elitebet-event-id: 0e8cd393-391c-42e3-92d7-a92db0096e70
elitebet-timestamp: 1785393916
elitebet-signature: sha256=<hex-hmac>
The signature is calculated over the exact raw request body:
HMAC_SHA256(webhookSecret, timestamp + "." + rawRequestBody)
- Read the body as raw bytes before parsing JSON.
- Reject timestamps older than five minutes.
- Compare the signature in constant time.
- Check the header and payload event IDs match.
- Grant each
eventIdonly once. - Return the acknowledgement below after durable processing.
{
"status": "Success",
"statuscode": 200
}