External Games
External games let you run a third-party HTML5 game inside a sandboxed iframe on the member portal or campaign landing. The vendor brings the visuals and play loop; the platform owns the outcome — the prize lottery still runs server-side so prize stock is always under your control.
When to use it
- You want a game that's not in the built-in catalogue — a branded mini-game custom-built by an agency, or a polished third-party game from a games marketplace.
- You want the visual polish of a specialised game studio without losing the platform's prize, stock, and fraud controls.
- You're piloting a new game type before asking for it in the built-in catalogue.
How it works
A game definition is a host-managed record that points at the vendor's HTML5 bundle. The bundle is hosted on the platform's CDN and locked down with a Subresource Integrity (SRI) hash so a tampered bundle won't run.
When a member plays:
- The member portal opens a session via
POST /api/services/app/MemberExternalGames/StartSessionand gets back a short-lived play token (5 minutes, single use). - The iframe loads with the bundle URL and SRI hash. Sandbox:
allow-scriptsonly (no top-level navigation, no popups, no same-origin access). - The vendor's game uses the
@incentia/games-sdkpackage to read the play context (readPlayContext()) and callcomplete(signal)when the session ends. - The platform receives the signal (a small JSON payload the vendor defines), validates it against the game definition's signal schema, and runs the standard prize lottery.
- The iframe receives the outcome via a
postMessagecallback and shows the appropriate "you won" or "no prize" UI.
Because the outcome decision happens server-side, no client tampering can change what prize a member wins. The vendor only owns presentation.
Step-by-step
Publish a game definition (host admin)
External-game definitions are managed at the host (platform-operator) level, not the tenant — so they're shared across tenants.
- Log in as a host admin.
- Go to Host → External games → Definitions.
- Click Upload bundle:
- Game ID — pick the catalogue entry the bundle belongs to.
- Version — semver string.
- Bundle zip — the vendor's HTML5 bundle (≤ 25 MB).
- Signal schema — JSON Schema describing the signal payload (typically
{ score: number, completionTime: number }or similar).
- The platform validates the zip (path traversal, file count, size), stores it on the CDN, and computes an SRI hash.
- The definition is published and immutable — re-uploading the same version returns 409. Bump the version to ship changes.
Subscribe a tenant to a definition
Each tenant opts in independently — definitions don't auto-apply.
- As a host admin, open the definition and click Add subscription.
- Pick the tenant and (optionally) an expiry.
- Save. The tenant now sees this definition as an available game in their catalogue.
Wire the game in a tenant
- As tenant admin, go to Gamification → Games and click Create game.
- Pick the External game type and select the definition.
- Configure prizes and probability the same way as built-in games.
- Save and attach to a campaign.
Vendor integration
If you're a vendor building a bundle for the platform, install the SDK:
npm install @incentia/games-sdk
In your bundle:
import { readPlayContext, complete } from '@incentia/games-sdk';
const ctx = readPlayContext(); // { sessionId, memberId, playToken }
// ... run the game ...
complete({ score: 1024, completionTime: 47.2 });
The SDK handles postMessage wire-up; you just decide what's in the signal. The exact schema must match the Signal schema registered with the game definition.
Limits and gotchas
- Same-origin denied. The iframe sandbox blocks
window.parentaccess — the SDK is the only way to talk to the host. - Play token is single-use and expires in 5 minutes. A vendor can't cache and replay a token.
- Rate limit: 10 plays per (member, definition) per hour. Above that the server returns 429.
- Signal payload is capped at 16 KB. Larger payloads are rejected.
- Definition is immutable. Bumping a version means a new definition row; the platform never mutates an existing definition's bundle.
- No Flutter parity. The mobile (Flutter) app does not currently render external games — they're web-only.
- No revenue share. Per-play billing for vendors is not yet implemented.
- Permission:
Pages.Games.ExternalDefinitions(host scope only).