# Supplier Games SDK

The Supplier Games SDK is a way for game suppliers to **deliver** their **games** **directly** into an **operator's website** — without iframes — while enabling real-time, two-way communication between the game and the operator.

Instead of only providing a game URL for Hub88 to load in an iframe, the supplier provides a **JavaScript file** (the SDK) that **Hub88** loads and **runs natively** on the operator's page. This gives players a faster, smoother experience and unlocks new capabilities like operator-controlled sound, theme switching, and live game event tracking.

{% hint style="info" %}
**Compatibility notice**

This SDK is a communication layer between the supplier's game and the operator's website. It only works when the operator is using the [**Hub88 Operator Games API SDK**](/developer-docs/operator-sdks/operator-games-api-sdk.md) on their side.&#x20;

If the operator uses a traditional iframe integration, the game will still load via your existing iframe/URL integration — but none of the SDK features (events, actions) will be available.
{% endhint %}

## How the SDK Integration Work?&#x20;

The Supplier Games SDK adds a frontend layer on top of your existing Supplier API integration. To understand what you're building, it helps to see the full flow, from the operator requesting a game launch, through to the game running on the player's screen.

There are **three phases**:

#### Phase 1: Server-side session setup

Before anything loads in the player's browser, Hub88 sets up a session between the operator and your backend.

1. The **Operator's backend** sends a game launch request to **Hub88**.
2. Hub88 calls your(supplier's) backend's [`/game/authorize`](#post-game-authorize-endpoint-reference) endpoint with session details (player token, game code, platform, currency, operator ID, and country).
3. Your backend initialises a player session and responds. If your SDK needs custom configuration at launch time (environment URLs, feature flags, theme settings, etc.), return them as `game_sdk_init_params` in your response — these will be merged into the `config.meta` object your SDK receives in Phase 2.
4. Hub88 returns a token to the operator, with your SDK params merged in.
5. The operator passes this token to their frontend.

{% hint style="info" %}
**`POST /game/authorize` vs `POST/game/url`**

`POST/`[`game/authorize`](#post-game-authorize) is used for SDK-based game launches. Your existing [`POST /game/url`](/developer-docs/supplier-api-reference/games-api.md#post-game-url) endpoint continues to be used for traditional iframe-based launches. Both endpoints remain active — which one Hub88 calls depends on whether the operator is using the Operator Games API SDK or a traditional iframe integration.
{% endhint %}

#### Phase 2: Client-side SDK initialisation

Once the operator's frontend has the token, the Hub88 Operator Games API SDK takes over:

1. The operator creates a `Hub88Games` instance and calls `.initialize()`.
2. The Hub88 SDK validates the token.
3. The Hub88 SDK dynamically imports your JavaScript file from the CDN URL configured for your integration.
4. The Hub88 SDK instantiates your `GenericSupplierGames` class and calls `init(config)` with session details — including any `game_sdk_init_params` you returned in Phase 1, merged into `config.meta`.
5. Your SDK loads game assets, authenticates with your backend, and renders the game inside the operator's page.
6. When your `init()` Promise resolves, the game is live.

This is the phase covered in detail by the 6-step build guide below.

#### Phase 3 — Game running

With the game loaded, two-way communication flows between the operator and your game:

* **Operator → Game:** The operator sends commands via `sendAction()` (standard actions like muting sound) and `sendCustomAction()` (custom actions you define, like theme switching or turbo mode).
* **Game → Operator:** Your SDK dispatches events (`game-play-started`, `game-play-ended`, `game-notification`, `game-custom-event`) to inform the operator about gameplay state.

Wallet transactions (bets, wins, rollbacks) continue to flow through the Supplier Wallet API as they do today — the SDK handles frontend communication only.

#### Integration flow diagram

```mermaid
sequenceDiagram
    participant OpBack as Operator Backend
    participant Hub88Back as Hub88 Backend API
    participant SupBack as Supplier Backend
    participant OpFront as Operator Frontend
    participant Hub88SDK as Hub88 Games SDK
    participant CDN as Supplier CDN
    participant SupSDK as Supplier SDK (GenericSupplierGames)

    Note over OpBack,SupBack: Phase 1 — Server-side session setup
    OpBack->>Hub88Back: POST /game/authorize
    Hub88Back->>SupBack: POST {api_url}/game/authorize
    Note over SupBack: Initialises player session,<br/>responds with parameters
    SupBack-->>Hub88Back: 200 OK { game_sdk_init_params, ... }
    Hub88Back-->>OpBack: Hub88 token (sdk_params merged in)
    OpBack-->>OpFront: Token passed to frontend

    Note over OpFront,SupSDK: Phase 2 — Client-side SDK initialisation
    OpFront->>Hub88SDK: new Hub88Games(config) + .initialize()
    Hub88SDK->>Hub88SDK: Validate Hub88 token
    Hub88SDK->>CDN: import(sdkUrl)
    CDN-->>Hub88SDK: ES module { GenericSupplierGames }
    Hub88SDK->>SupSDK: new GenericSupplierGames()
    Hub88SDK->>SupSDK: await init(config)
    SupSDK->>SupBack: Load game assets / session
    SupBack-->>SupSDK: Game ready
    SupSDK-->>Hub88SDK: Promise resolves
    Hub88SDK-->>OpFront: .initialize() resolves

    Note over OpFront,SupSDK: Phase 3 — Game running
    OpFront->>Hub88SDK: sendAction("setSound", payload)
    Hub88SDK->>SupSDK: sendAction("setSound", payload)
    SupSDK-->>Hub88SDK: CustomEvent "hub88-game-sdk-supplier-event"
    Hub88SDK-->>OpFront: "hub88-game-sdk-event" + instanceId
```

### What does the supplier need to build?

As a supplier integrating with Hub88, you need **three things**:

<table><thead><tr><th width="40">#</th><th width="288.7786865234375">What</th><th>Why</th></tr></thead><tbody><tr><td>1</td><td><strong>Standard Supplier API integration</strong> (iframe / game URL)</td><td>This is your existing backend integration — player authorisation, wallet transactions (bets, wins, balance), freebets, and providing a game URL. Every supplier must have this.</td></tr><tr><td>2</td><td><strong>Supplier Games SDK</strong> (this guide)</td><td>This is the new frontend integration — <strong>a JavaScript file</strong> that lets your game run natively on the operator's page and communicate with the operator in real time and faster. </td></tr><tr><td>3</td><td><strong><code>/game/authorize</code> endpoint</strong></td><td>A new backend endpoint that Hub88 calls for SDK-based game launches. It serves the same session setup purpose as <code>/game/url</code> but does not return a game URL (since the SDK loads the game client-side). See the endpoint reference.</td></tr></tbody></table>

The SDK does **not** replace your Supplier API integration. It adds a frontend communication layer on top of it.

{% hint style="info" %}
**Some operators will use the new Operator Games API SDK** (and benefit from your SDK integration), while others will still use the traditional iframe approach.&#x20;

Your game must continue to work via the iframe/URL method. **The SDK is an additional delivery channel, not a replacement.**
{% endhint %}

***

### Getting Started

Before building the SDK, make sure you already have these in place:

<table><thead><tr><th width="581.1614990234375">Prerequisite</th><th>Status</th></tr></thead><tbody><tr><td>Supplier API integration with Hub88 (Games API, Wallet API, Freebets API)</td><td>Required</td></tr><tr><td>Your game loads via a URL (iframe integration)</td><td>Required</td></tr></tbody></table>

**On high level, this is how the SDK works:**

When an operator launches your game, Hub88:

1. Loads your JavaScript SDK from a CDN URL
2. Instantiates your `GenericSupplierGames` class
3. Calls `init(config)` with session details — your game renders and resolves
4. Sends actions to your game via `sendAction()` and `sendCustomAction()`

**Building the SDK is a 5-step process**. Each step is covered in detail in the sections below.

<table><thead><tr><th width="81.69964599609375">Step</th><th width="553.82470703125">What you do</th><th>Section</th></tr></thead><tbody><tr><td>1</td><td>Create a JavaScript ES module file and export the <code>GenericSupplierGames</code> class</td><td><a href="#step-1-create-the-javascript-file"><strong>Step 1</strong></a></td></tr><tr><td>2</td><td>Add the <code>constructor()</code></td><td><a href="#step-2-implement-the-constructor"><strong>Step 2</strong></a></td></tr><tr><td>3</td><td>Implement <code>init(config)</code> to load and render your game in the operator's page</td><td><a href="#step-3-implement-init-config-load-and-render-your-game"><strong>Step 3</strong></a></td></tr><tr><td>4</td><td>Implement <code>sendAction()</code> and <code>sendCustomAction()</code> to handle commands from the operator</td><td><a href="#step-4-handle-actions-from-the-operator"><strong>Step 4</strong></a></td></tr><tr><td>5</td><td>Dispatch events — tell the operator when rounds start, end, or something noteworthy happens</td><td><a href="#step-5-dispatch-events-tell-the-operator-what-is-happening"><strong>Step 5</strong></a></td></tr><tr><td>6</td><td>Host the file on a CDN, share the URL with Hub88, and validate using the playground tool</td><td><a href="#step-6-host-your-file-and-share-the-url"><strong>Step 6</strong></a></td></tr></tbody></table>

### Example file structure you can use to follow along

Hub88 will import this file from a CDN address you share, create an instance of your class, and call its methods. Use this file as a starting point and rework it according to your case.&#x20;

{% code overflow="wrap" lineNumbers="true" %}

```javascript
export class GenericSupplierGames {

  constructor() {
    // Set up internal state only. Do NOT load or render the game here.
    this._config = null;
    this._soundEnabled = true;
  }

  async init(config) {
    // Hub88 calls this to start your game. See Step 2.
  }

  sendAction(actionName, payload) {
    // Hub88 calls this when the operator sends a standard command. See Step 3.
  }

  sendCustomAction(actionName, payload) {
    // Hub88 calls this when the operator sends a custom command. See Step 3.
  }
}

export default GenericSupplierGames;
```

{% endcode %}

***

## Step 1: Create the JavaScript file

Your SDK must be a single JavaScript ES module file hosted at a stable CDN URL.

The file must:

* Run in a browser environment (no Node.js-specific APIs)
* Be a valid ES module that can be loaded via dynamic `import()`
* Export the `GenericSupplierGames` class (named export or default export)

As mentioned, your SDK must export the `GenericSupplierGames` class. You can use either a named export or a default export:

```javascript
// Named export (recommended)
export class GenericSupplierGames {
  // ... implementation
}

// OR default export
class GenericSupplierGames {
  // ... implementation
}
export default GenericSupplierGames
```

{% hint style="info" %}
The Hub88 SDK will check for both `module.GenericSupplierGames` (named export) and `module.default` (default export), so either approach works.

For maximum compatibility, you can export both (as shown in the [example SDK](#complete-example)).
{% endhint %}

***

## Step 2: Implement the constructor

Use the constructor to set up internal state.&#x20;

Do **not** load assets or render anything here — that happens in `init()`. The `constructor()` is called during Hub88 `initialize()` phase.

{% code overflow="wrap" lineNumbers="true" %}

```javascript
constructor() {
  this._config = null
  this._soundEnabled = true
}
```

{% endcode %}

***

## Step 3: Implement `init(config)` — Load and render your game

This is the most important part of your integration. Hub88 calls `init()` with a config object after the class is instantiated. \
\
Your game must render inside the container element and resolve the returned Promise when the game is ready to play.

#### The config object

<table><thead><tr><th>Field</th><th width="187">Type</th><th>Required/Optional?</th><th>What it is</th></tr></thead><tbody><tr><td><code>token</code></td><td>string</td><td>Required</td><td>Hub88 session token. Use this to authenticate with your backend.</td></tr><tr><td><code>gameCode</code></td><td>string</td><td>Required</td><td>Which game to load (e.g., <code>'game_abc_123'</code>).</td></tr><tr><td><code>platform</code></td><td>string</td><td>Required</td><td>Player's device: <code>'GPL_DESKTOP'</code> or <code>'GPL_MOBILE'</code>.</td></tr><tr><td><code>lobbyUrl</code></td><td>string</td><td>Required</td><td>Where to send the player when they exit the game.</td></tr><tr><td><code>lang</code></td><td>string</td><td>Required</td><td>Language code (e.g., <code>'en'</code>, <code>'de'</code>, <code>'ja'</code>).</td></tr><tr><td><code>containerId</code></td><td>string</td><td>Required</td><td>ID of the HTML element where your game should appear on the page.</td></tr><tr><td><code>depositUrl</code></td><td>string</td><td>Optional</td><td>URL for the deposit page if the player needs to add funds.</td></tr><tr><td><code>meta</code></td><td>object</td><td>Optional</td><td>Additional parameters — see "The meta field" section below.</td></tr></tbody></table>

#### Example

{% code overflow="wrap" lineNumbers="true" %}

```javascript
{
  token:       string,   // Hub88 provider token — use this to authenticate with your backend
  gameCode:    string,   // Game identifier (e.g., 'game_abc_123')
  platform:    string,   // Platform/device (e.g., 'GPL_DESKTOP', 'GPL_MOBILE')
  lobbyUrl:    string,   // URL to navigate to when the player exits the game
  lang:        string,   // Language code (e.g., 'en', 'es', 'de')
  containerId: string,   // ID of the DOM element where the game should render
  depositUrl:  string,   // (optional) URL to navigate to for deposits
  meta:        object    // (optional) Additional parameters — see below
}
```

{% endcode %}

<pre class="language-javascript" data-overflow="wrap" data-line-numbers><code class="lang-javascript"><strong>async init(config) {
</strong>  // 1. Find the container on the page
  const container = document.getElementById(config.containerId);
  if (!container) {
    throw new Error(`Container '${config.containerId}' not found`);
  }

  // 2. Authenticate with your backend using config.token
  const session = await fetch('https://your-rgs.com/api/session', {
    method: 'POST',
    body: JSON.stringify({ token: config.token, game: config.gameCode })
  }).then(r => r.json());

  // 3. Load game assets
  await this._loadGameAssets(session);

  // 4. Render the game inside the container
  this._renderGame(container, session);

  // Done — the Promise resolves and Hub88 knows the game is ready
}
</code></pre>

#### Rules

* **Must return a Promise** that resolves when the game is fully loaded and playable.
* **Must throw or reject** if something goes wrong — Hub88 will report the failure to the operator.
* **Must complete within 15 seconds.** If it takes longer, Hub88 aborts with a timeout error.
* **Must render inside `config.containerId`** — do not render anywhere else on the page.
* **Must handle `config.lobbyUrl`** — when the player wants to exit the game, navigate them to this URL.

{% hint style="warning" %}
**15-second timeout**&#x20;

Optimise your asset loading. If your game has large assets, consider showing a loading screen inside the container immediately (so the player sees activity) while assets download in the background. The Promise should still only resolve when the game is actually playable.
{% endhint %}

#### The `meta` field: Receiving custom configuration

The `meta` object inside `config` lets your SDK receive additional parameters. It combines data from three sources, merged in this priority order:

<table><thead><tr><th width="114.2213134765625">Priority</th><th width="189.572021484375">Source</th><th>How it gets there</th></tr></thead><tbody><tr><td>Highest</td><td><strong>Operator meta</strong></td><td>The operator passes custom parameters when initialising the Operator Games API SDK</td></tr><tr><td>Medium</td><td><strong>Hub88 token validation</strong></td><td>Additional parameters from Hub88's session validation</td></tr><tr><td>Lowest</td><td><strong>Your authorize response</strong></td><td>Parameters you return as <code>game_sdk_init_params</code> in your <a href="#post-game-authorize-endpoint-reference"><code>/game/authorize</code></a> response</td></tr></tbody></table>

If the same key appears in multiple sources, the higher-priority source wins.

#### How to provide your own meta values?

When Hub88 calls your authorize endpoint during token validation, you can return an optional `game_sdk_init_params` object in the response. This object will be automatically merged into the `meta` field that your SDK receives.

```json
{
  "url": "https://your-rgs.com/game/abc",
  "game_sdk_init_params": {
    "theme": "dark",
  }
}
```

These values appear in `config.meta` when your `init()` is called:

```javascript
async init(config) {
  const theme = config.meta?.theme;       // "dark"
  const engine = config.meta?.engineUrl;  // "https://engine.yourstudio.com"
  // ...
}
```

{% hint style="info" %}
**Use `meta` for any supplier-specific configuration your SDK needs — environment URLs, feature flags, RTP settings, visual themes, etc.**&#x20;

Document your supported meta parameters so operators know what they can customise.
{% endhint %}

***

## Step 4: Handle actions from the operator

Once your game is running, the operator can send commands to it. The operator can call actions on your game at any time after `init()` resolves. Hub88 forwards these commands by calling your `sendAction()` and `sendCustomAction()` methods.

This is what makes the SDK powerful — operators can control aspects of your game in real time without any custom integration work.

### 4.1 Standard actions — `sendAction(actionName, payload)`

These are actions defined by Hub88 that **all suppliers should support**.

**Required: `setSound`**

Every supplier must handle this action:

{% code overflow="wrap" lineNumbers="true" %}

```javascript
sendAction(actionName, payload) {
  switch (actionName) {
    case 'setSound':
      // payload.enabled — true (unmute) or false (mute)
      // payload.level   — 0 to 100 (optional volume level)
      this._soundEnabled = payload.enabled;
      if (typeof payload.level === 'number') {
        this._setSoundLevel(payload.level);
      }
      break;
    default:
    // Unknown actions can be silently ignored
      console.warn('Unknown action:', actionName);
  }
}
```

{% endcode %}

### 4.2 Custom actions — `sendCustomAction(actionName, payload)`

These are actions that you and the operator agree on together. You define what actions your game supports, and the operator can trigger them. No Hub88 involvement is needed to add new custom actions — this is a direct supplier–operator agreement.

```javascript
sendCustomAction(actionName, payload) {
  switch (actionName) {
    case 'setTheme':
      this._applyTheme(payload.theme);
      break;
    case 'enableTurboMode':
      this._setTurboMode(payload.enabled);
      break;
    default:
      console.warn('Unknown custom action:', actionName);
  }
}
```

{% hint style="info" %}
**Unknown actions should be silently ignored (or logged with a console warning).**&#x20;

Never throw an error for an unrecognised action — it could be intended for a different game or a future feature the operator is testing.
{% endhint %}

#### Real-world use case examples

Here are practical examples of how operators benefit from actions:

<table><thead><tr><th>Action</th><th width="138.5755615234375">Type</th><th>What happens</th><th>Why operators want this</th></tr></thead><tbody><tr><td><code>setSound</code></td><td>Standard</td><td>Operator mutes/unmutes the game</td><td>The casino site has its own audio controls. When a player mutes the site, all games should go silent.</td></tr><tr><td><code>setTheme</code></td><td>Custom</td><td>Operator switches the game between light/dark mode</td><td>The casino site has a dark theme. The game should match — no jarring white game inside a dark page.</td></tr><tr><td><code>enableTurboMode</code></td><td>Custom</td><td>Operator enables fast-play mode</td><td>Some operators want to offer "turbo" as a site-wide toggle for all supported games.</td></tr><tr><td><code>showPromo</code></td><td>Custom</td><td>Operator triggers an in-game promotional banner</td><td>The operator is running a promotion and wants the game to display a banner without needing a game update.</td></tr><tr><td><code>setLanguage</code></td><td>Custom</td><td>Operator changes the in-game language on the fly</td><td>The player switches language on the casino site. The game should update immediately without reloading.</td></tr></tbody></table>

***

## Step 5: Dispatch events — Tell the operator what is happening

Your SDK communicates game state back to the operator by dispatching `CustomEvent` instances on `window` with the type `hub88-game-sdk-supplier-event`.

{% code overflow="wrap" lineNumbers="true" %}

```javascript
window.dispatchEvent(new CustomEvent('hub88-game-sdk-supplier-event', {
  detail: { /* event object */ }
}))
```

{% endcode %}

The Hub88 SDK validates each event before forwarding it to the operator. Invalid events are silently dropped with a console warning. Always include all required fields, depending on the event type.

**Multi-instance support**

The Hub88 SDK automatically injects an `instanceId` field (set to the `containerId`) into each event when re-dispatching to the operator. This allows operators to identify which game instance dispatched the event when multiple games are running on the same page.&#x20;

You do not need to include `instanceId` in your events, as the Hub88 SDK adds it automatically.

### Event types

There are four event types. Each has specific required fields.

#### `game-play-started` — A round begins

Dispatch when a game round starts (e.g., the player places a bet and their balance is locked).

**Use case:** The operator displays a "round in progress" indicator on their UI, or disables the deposit button while a round is active.

{% code overflow="wrap" lineNumbers="true" %}

```javascript
{
  name:      'game-play-started',  // required
  timestamp: Date.now(),           // required — Unix ms, must be a recent timestamp
  kind:      'bet',                // required — 'bet' | 'win' | 'loss' | 'unknown'
  balance:   number,               // required — player balance after bet, as a number
  currency:  'EUR'                 // required — ISO 4217 currency code
}
```

{% endcode %}

{% code title="Example event for gameplay started" overflow="wrap" lineNumbers="true" %}

```javascript
this._dispatch({
  name:      'game-play-started',
  timestamp: Date.now(),           // Unix milliseconds
  kind:      'bet',                // 'bet' | 'win' | 'loss' | 'unknown'
  balance:   950,                  // Player balance AFTER the bet (must be a number)
  currency:  'EUR'                 // ISO 4217 currency code
});
```

{% endcode %}

#### `game-play-ended` — A round ends

Dispatch when a game round ends (e.g., the outcome is calculated and the balance is updated).

**Use case:** The operator shows a win celebration animation on their site, updates the displayed balance, or logs the round result for analytics.

{% code overflow="wrap" lineNumbers="true" %}

```javascript
{
  name:      'game-play-ended',  // required
  timestamp: Date.now(),         // required
  kind:      'win',              // required — 'bet' | 'win' | 'loss' | 'unknown'
  balance:   number,             // required — player balance after outcome
  currency:  'EUR',              // required
  winAmount: number              // optional — amount won (include on wins)
}
```

{% endcode %}

{% code title="Example event dispatch for gameplay ended" overflow="wrap" lineNumbers="true" %}

```javascript
this._dispatch({
  name:      'game-play-ended',
  timestamp: Date.now(),
  kind:      'win',                // 'bet' | 'win' | 'loss' | 'unknown'
  balance:   1200,                 // Player balance AFTER the outcome
  currency:  'EUR',
  winAmount: 250                   // (optional) Amount won — include on wins
});
```

{% endcode %}

#### `game-notification` — Something noteworthy happened

Dispatch to send informational alerts to the operator.

**Use case:** The operator displays a toast notification ("Bonus round unlocked!"), or logs warnings/errors for support purposes.

{% code overflow="wrap" lineNumbers="true" %}

```javascript
{
  name:      'game-notification',  // required
  timestamp: Date.now(),           // required
  level:     'info',               // required — 'info' | 'warning' | 'error'
  code:      'BONUS_TRIGGERED',    // required — machine-readable event code
  message:   'Bonus round started' // required — human-readable description
}
```

{% endcode %}

{% code title="Example event dispatch for a game notification" overflow="wrap" lineNumbers="true" %}

```javascript
this._dispatch({
  name:      'game-notification',
  timestamp: Date.now(),
  level:     'info',               // 'info' | 'warning' | 'error'
  code:      'BONUS_TRIGGERED',    // Machine-readable code
  message:   'Bonus round started' // Human-readable description
});
```

{% endcode %}

#### `game-custom-event` — Anything else

Dispatch for supplier-specific events that don't fit the standard types.

**Use case:** The operator builds a custom dashboard showing live game state (e.g., "7 free spins remaining"), or triggers cross-game promotions based on game events.

{% code overflow="wrap" lineNumbers="true" %}

```javascript
{
  name:      'game-custom-event',       // required
  timestamp: Date.now(),                // required
  event:     { feature: 'bonus', ... } // required — any object with your custom data
}
```

{% endcode %}

{% code title="Example dispatch of an event for custom event" overflow="wrap" lineNumbers="true" %}

```javascript
this._dispatch({
  name:      'game-custom-event',
  timestamp: Date.now(),
  event:     {                     // Must be an object (not a string, array, or null)
    feature: 'free-spins',
    spinsRemaining: 7
  }
});
```

{% endcode %}

### Validation rules for Events

Hub88 checks every event before forwarding. Events that fail are silently dropped.

<table><thead><tr><th width="206.0833740234375">Field</th><th>Rule</th></tr></thead><tbody><tr><td><code>name</code></td><td>Must be one of the four event names listed above</td></tr><tr><td><code>timestamp</code></td><td>Must be a number greater than 0. Use <code>Date.now()</code>.</td></tr><tr><td><code>kind</code></td><td>Must be <code>'bet'</code>, <code>'win'</code>, <code>'loss'</code>, or <code>'unknown'</code></td></tr><tr><td><code>level</code></td><td>Must be <code>'info'</code>, <code>'warning'</code>, or <code>'error'</code></td></tr><tr><td><code>balance</code></td><td>Must be a number (not a string like <code>"950"</code>)</td></tr><tr><td><code>event</code></td><td>Must be a plain object (not a string, array, or null)</td></tr><tr><td>All required fields</td><td>Must be present and not <code>undefined</code></td></tr></tbody></table>

***

## Step 6: Host your file and share the URL

1. **Bundle** your SDK into a single ES module file. Use a bundler like Webpack, Rollup, or esbuild if you use external dependencies.
2. **Host** it on a stable CDN URL (e.g., `https://cdn.yourstudio.com/hub88-sdk/v1.0.0/game-sdk.js`).
3. **Share** the URL with Hub88. Hub88 configures it in the supplier contract under `sdk_url` in `api_props`.

{% hint style="info" %}
**Include a version number in your URL path (e.g., `/v1.0.0/`).**&#x20;

When you release updates, deploy to a new version URL. This ensures existing live sessions are not disrupted by a mid-session code change.
{% endhint %}

***

## Error handling

| Scenario                                      | What happens                                                                  |
| --------------------------------------------- | ----------------------------------------------------------------------------- |
| `constructor()` throws                        | Hub88 reports initialisation failure to the operator. The game does not load. |
| `init()` throws or rejects                    | Hub88 reports the error. The game does not launch.                            |
| `init()` takes longer than 15 seconds         | Hub88 aborts with a timeout error.                                            |
| An event has missing or invalid fields        | The event is silently dropped. The game keeps running normally.               |
| `sendAction()` or `sendCustomAction()` throws | The error is passed back to the operator.                                     |

{% hint style="info" %}
**Hub88 captures logs for SDK lifecycle events (initialisation, errors, timeouts).**

If you experience issues during integration testing, contact Hub88 support for log access.
{% endhint %}

***

## Multi-Instance Support

The Hub88 SDK supports running **multiple games simultaneously on the same page**. This works seamlessly with the Generic Supplier API and ES modules:

* **Module deduplication**: The browser automatically caches ES modules. If the same SDK URL is imported multiple times via `import()`, the browser returns the same module instance from cache.
* **Instance isolation**: Each `GenericProvider` instance creates its own instance of your `GenericSupplierGames` class, ensuring complete isolation between games.
* **Event routing**: Each game instance dispatches events with an `instanceId` (the `containerId`) so operators can identify which game fired which event.

**What this means for you:**

* You do not need to change anything in your SDK to support multi-instance scenarios.
* Export your class using ES module syntax (`export class` or `export default`)
* Each game on the page will have its own instance of your class with its own state
* Events you dispatch will automatically include `instanceId` when forwarded to the operator.

***

## Validate before going live

Hub88 provides a browser-based validator tool to check your SDK before submitting.

1. Open the Hub88 playground page.
2. Enter your SDK CDN URL in the **Load SDK** input.
3. Click **Load** to import your SDK.
4. Click **Validate SDK** to run the automated checks.

The validator confirms:

* Your class is exported correctly
* It can be instantiated
* `init()`, `sendAction()`, and `sendCustomAction()` methods exist

***

## Complete example

Use this as a starting point. Replace the placeholder logic with your real game.

```javascript
export class GenericSupplierGames {

  constructor() {
      // Set up initial state. Do not load or render the game here.
    this._config = null;
    this._soundEnabled = true;
  }

  async init(config) {
    if (!config || !config.token || !config.gameCode || !config.containerId) {
      throw new Error('Missing required config fields');
    }

    this._config = config;

    const container = document.getElementById(config.containerId);
    if (!container) {
      throw new Error(`Container '${config.containerId}' not found`);
    }

    // ──────────────────────────────────────────────
    // Replace this section with your real game logic to load your game here
    // 1. Authenticate with your backend using config.token
    // 2. Load game assets
    // 3. Render game inside the container
    container.innerHTML = '<p>Game loaded</p>';
    // ──────────────────────────────────────────────

    // Dispatch game-play-started when the first round begins
   // (this example dispatches immediately on load for demonstration)
    this._dispatch({
      name: 'game-play-started',
      timestamp: Date.now(),
      kind: 'bet',
      balance: 1000,
      currency: 'EUR'
    });
  }

  sendAction(actionName, payload) {
    if (actionName === 'setSound') {
      this._soundEnabled = payload.enabled;
      if (typeof payload.level === 'number') {
        // Apply volume level (0–100)
      }
    }
  }

  sendCustomAction(actionName, payload) {
    // Handle any custom actions agreed with the operator.
    // Example:
    // if (actionName === 'setTheme') {
    //   this._applyTheme(payload.theme);
    // }
  }

  _dispatch(eventData) {
    window.dispatchEvent(new CustomEvent('hub88-game-sdk-supplier-event', {
      detail: eventData
    }));
  }
}
// Optional: also export as default for maximum compatibility
export default GenericSupplierGames;
```

***

### `POST /game/authorize` Endpoint Reference

When an operator launches a game via the SDK path, Hub88 calls your `/game/authorize` endpoint to set up the player session. This is the SDK equivalent of `/game/url` — it serves the same purpose (initialising a session) but does not return a game URL, since the game is loaded client-side by the SDK instead.

{% hint style="info" %}
Your existing [`/game/url`](/developer-docs/supplier-api-reference/games-api.md#post-game-url) endpoint is still used for iframe-based launches. Both endpoints remain active.
{% endhint %}

***

#### POST /game/authorize

Hub88 sends this request to your backend at `{your_api_url}/game/authorize` when an operator launches a game using the Operator Games API SDK.

**Headers**

| Header              | Description                                                                       |
| ------------------- | --------------------------------------------------------------------------------- |
| `X-Hub88-Signature` | HMAC signature of the JSON-encoded request body. Verify using Hub88's public key. |
| `Content-Type`      | `application/json`                                                                |

**Request body**

| Parameter        | Type           | Required    | Description                                                                                          |
| ---------------- | -------------- | ----------- | ---------------------------------------------------------------------------------------------------- |
| `user`           | string or null | Yes         | The supplier-mapped user identifier. `null` when `currency` is `"XXX"` (demo/fun mode).              |
| `token`          | string         | Yes         | Supplier session token for authentication.                                                           |
| `platform`       | string         | Yes         | Player's platform: `"GPL_DESKTOP"` or `"GPL_MOBILE"`.                                                |
| `operator_id`    | string         | Yes         | The supplier-assigned operator identifier.                                                           |
| `currency`       | string         | Yes         | ISO 4217 currency code. May be aliased via Hub88's currency alias configuration.                     |
| `sub_partner_id` | string         | Yes         | Sub-partner (brand/whitelabel/site) identifier.                                                      |
| `country`        | string         | Yes         | Player's ISO 3166-1 country code.                                                                    |
| `meta`           | object         | Yes         | Session metadata. Contents vary by operator and context.                                             |
| `game_code`      | string         | Conditional | Game identifier. Included when the game is identified by code.                                       |
| `game_id`        | integer        | Conditional | Game identifier. Included when the game is identified by numeric ID (and no `game_code` is present). |
| `ip`             | string         | Conditional | Player's IP address. Included only when the supplier is configured to receive client IPs.            |

**Example request**

{% code overflow="wrap" lineNumbers="true" %}

```json
{
  "user": "player_abc_123",
  "token": "f562a685-a160-4d17-876d-ab3363db331c",
  "platform": "GPL_DESKTOP",
  "operator_id": "1",
  "currency": "EUR",
  "sub_partner_id": "my-casino-brand",
  "country": "EE",
  "meta": {},
  "game_code": "clt_dragonrising"
}
```

{% endcode %}

**Expected response**

Return `200 OK` with a JSON body. Non-200 responses are treated as errors and the game will not launch.

<table><thead><tr><th width="208.1893310546875">Field</th><th width="79.0738525390625">Type</th><th width="88.4027099609375">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>game_sdk_init_params</code></td><td>object</td><td>Optional</td><td>Custom parameters to pass to your SDK at initialisation. These are merged into the <code>config.meta</code> object your <code>init()</code> method receives. Use this for environment URLs, feature flags, theme defaults, RTP settings, or any configuration your SDK needs.</td></tr></tbody></table>

**Example response sample**

{% code overflow="wrap" lineNumbers="true" %}

```json
{
  "game_sdk_init_params": {
    "theme": "dark",
    "engineUrl": "https://engine.yourstudio.com/v2",
    "features": {
      "turboMode": true,
      "autoPlay": false
    }
  }
}
```

{% endcode %}

**How `game_sdk_init_params` reaches your SDK?**

The values you return here are merged into `config.meta` when Hub88 calls your SDK's `init(config)` method. They have the lowest priority in the merge order — if the operator or Hub88 token validation provides the same key, those values take precedence. See the meta field documentation for full merge priority details.

***

### Pre-submission checklist

Before sharing your SDK URL with Hub88:

* [ ] You have a working Supplier API integration (Games API, Wallet API)
* [ ] Your game works via the existing iframe/URL method
* [ ] SDK is a single JavaScript ES module file
* [ ] File is hosted at a stable, versioned CDN URL
* [ ] `GenericSupplierGames` class is exported (named + default)
* [ ] `constructor()` only sets up internal state — no rendering, no network calls
* [ ] `init(config)` renders the game inside `config.containerId`
* [ ] `init(config)` returns a Promise that resolves when the game is playable
* [ ] `init(config)` completes within 15 seconds
* [ ] &#x20;`sendAction('setSound', { enabled, level? })` is implemented
* [ ] All events use `window.dispatchEvent(new CustomEvent('hub88-game-sdk-supplier-event', { detail: ... }))`
* [ ] All events include `name`, `timestamp`, and all required fields
* [ ] Game navigates to `config.lobbyUrl` when the player exits
* [ ] SDK validated using the Hub88 playground tool
* [ ] `/game/authorize` endpoint is implemented and responds with `200 OK`
* [ ] `/game/authorize` returns `game_sdk_init_params` if your SDK needs custom configuration at launch
* [ ] `/game/authorize` and `/game/url` both remain operational (iframe launches must continue to work)
* [ ] CDN URL shared with Hub88

***

### FAQ

**Does this replace my existing Supplier API integration?** \
No. The SDK is a frontend communication layer. Your backend integration (Games API, Wallet API, Freebets API) remains unchanged and is still required. See the [Supplier API Overview](https://docs.hub88.io/developer-docs/supplier-api-reference/supplier-api-overview).

**Does the operator need to update anything to use my SDK?** \
The operator must be using the Hub88 Operator Games API SDK. If they are, your SDK works automatically — no operator-side code changes are needed to add a new supplier. If the operator still uses iframes, your game loads via the traditional URL method instead.

**Can I add new custom actions later without updating Hub88?** \
Yes. Custom actions (`sendCustomAction`) are a direct agreement between you and the operator. You define the action names and payloads, the operator calls them. No Hub88 update or intervention is needed.

**Do game transactions (bets, wins) go through the SDK?**&#x20;

No. Transactions continue to flow through the Supplier Wallet API as they do today. The SDK handles frontend concerns only — game rendering, UI actions (sound, theme), and informational events (round started, round ended). It does not process money.

**What if my SDK file is unavailable (CDN outage)?** \
The game will fail to load via the SDK path. Hub88 will report the error. Ensure your CDN is reliable, and consider having a fallback URL.

**What browsers must my SDK support?** \
All modern browsers that support ES modules: Chrome, Firefox, Safari, and Edge (current versions).

**Can I use npm packages in my SDK?** \
Yes, but your final output must be a single bundled ES module file. Use a bundler (Webpack, Rollup, esbuild) to combine everything.

**How do I test locally?** \
Use the Hub88 playground validator, Technical Operations team can provide access to it. During development, you can point it at a local dev server URL to test before deploying to your CDN.

**What is `/game/authorize` and how does it differ from `/game/url`?**\
`/game/authorize` is called by Hub88 when an operator launches your game using the Operator Games API SDK. It initialises a player session — the same way `/game/url` does — but does not return a game URL, because the SDK loads the game client-side instead. Your existing `/game/url` endpoint continues to be used for iframe-based launches. Both endpoints must remain operational.

**Can I return different configuration for SDK launches vs iframe launches?**\
Yes. Since `/game/authorize` and `/game/url` are separate endpoints, you can tailor your response for each. For example, you might return `game_sdk_init_params` with theme or feature flag settings in `/game/authorize` that aren't relevant for iframe launches.

***

### Glossary

| Term                       | Definition                                                                                                                                                                                                 |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Supplier**               | A game studio or distributor that provides games through Hub88                                                                                                                                             |
| **Operator**               | An online casino brand that offers games to players                                                                                                                                                        |
| **Hub88**                  | The aggregation platform connecting suppliers and operators                                                                                                                                                |
| **Supplier API**           | Hub88's backend API that suppliers use for player authorisation, bets, wins, and balance. Documented at [docs.hub88.io](https://docs.hub88.io/developer-docs/supplier-api-reference/supplier-api-overview) |
| **Operator Games API SDK** | Hub88's frontend SDK used by operators to load and manage games on their website. The Supplier Games SDK communicates through this.                                                                        |
| **ES module**              | A standard JavaScript module format using `export` / `import` syntax                                                                                                                                       |
| **CDN**                    | Content Delivery Network — hosts and delivers files globally with fast load times                                                                                                                          |
| **DOM container**          | An HTML element on the page (like a `<div>`) where the game renders                                                                                                                                        |
| **RGS**                    | Remote Gaming Server — the supplier's backend that runs game logic                                                                                                                                         |
| **meta**                   | A flexible configuration object passed to the SDK during initialisation                                                                                                                                    |


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hub88.io/developer-docs/supplier-api-reference/supplier-games-sdk.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
