Saltar al contenido principal

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:

  1. The member portal opens a session via POST /api/services/app/MemberExternalGames/StartSession and gets back a short-lived play token (5 minutes, single use).
  2. The iframe loads with the bundle URL and SRI hash. Sandbox: allow-scripts only (no top-level navigation, no popups, no same-origin access).
  3. The vendor's game uses the @incentia/games-sdk package to read the play context (readPlayContext()) and call complete(signal) when the session ends.
  4. 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.
  5. The iframe receives the outcome via a postMessage callback 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.

  1. Log in as a host admin.
  2. Go to Host → External games → Definitions.
  3. 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).
  4. The platform validates the zip (path traversal, file count, size), stores it on the CDN, and computes an SRI hash.
  5. 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.

  1. As a host admin, open the definition and click Add subscription.
  2. Pick the tenant and (optionally) an expiry.
  3. Save. The tenant now sees this definition as an available game in their catalogue.

Wire the game in a tenant

  1. As tenant admin, go to Gamification → Games and click Create game.
  2. Pick the External game type and select the definition.
  3. Configure prizes and probability the same way as built-in games.
  4. 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.parent access — 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).