> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.drimify.com/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Widget lifecycle and error handling

This article is for developers who embed a Drimify experience with the HTML integration code (widget) and need to react to what happens inside it: show a loader, detect errors, or track which screen the participant is on.

For setup and the full list of callbacks, see **Advanced integration of the HTML integration code (widget) via the API**.

# Event order when the widget loads

When you call `widget.load()`, events always arrive in this order:

1. **`onReady`** fires once, as soon as the experience has loaded its configuration (texts, theme, layout). This happens **before** the first screen is requested.
2. **`onRouteChange`** fires each time a screen is displayed, starting with the first one.

What this means in practice:

* **`onReady` doesn't mean the first screen is visible.** The first `onRouteChange` marks that.
* **`onError` can still fire after `onReady`**, for example if the first screen fails to load.
* **If the experience can't start at all, `onReady` never fires.** You receive `onError` instead (see below).

| Use `onReady` for "the experience has started". Use the first `onRouteChange` for "the participant can see content".

# Screen names (`onRouteChange`)

Every `onRouteChange` event has this shape:

```
{
  type: "routechange",
  data: {
    screen: "screen_<name>-<APP_ID>[-<suffix>]",
    errorCode: "ERR_xx"   // only on some unavailability screens
  }
}
```

Examples of regular screens:

* `screen_start-12345`
* `screen_end-12345`
* Loyalty Hub: `screen_loyalty-hub-<tab>-12345`, `screen_loyalty-hub-reward-12345-<reward_id>`

Match screens by their **prefix** (for example `event.data.screen.startsWith('screen_end-')`) rather than the full value: the suffix can change.

## Screens that mean the participant can't play

These screens are delivered as a normal `onRouteChange`, not as an error. The experience is working, but it shows the participant a message instead of the game.

| Screen name starts with | Meaning | `errorCode` |
| ---- | ---- | ---- |
| `screen_outofdate-` | The experience isn't available at this time | `ERR_02` not started yet, `ERR_03` ended, `ERR_18` between two activation periods, `ERR_19` outside the opening hours |
| `screen_nomoreviews-` | The participation quota has been reached | `ERR_04` |
| `screen_noquestions-` | The experience has no question to display | `ERR_16` |
| `screen_langnotavailable-` | The game isn't available in the selected language | — |
| `screen_alreadyplayed-<id>-ip` / `-session` | The participant has already played (limit per IP address or per session) | — |
| `screen_restriction-<id>-session` | Access control: this `session_uid` isn't on the authorised list | — |
| `screen_restriction-<id>-session_unique` | Access control: this `session_uid` has already been used | — |
| `screen_restriction-<id>-country` / `-ip` / `-query_string` | Blocked by country, IP address or URL parameter | — |
| `screen_restriction-<id>-jwt` | JWT authentication failed | — |
| `screen_form-error-` | A form or login was refused (blocked email domain, restricted list, already used, invalid format) | — |
| `screen_loyalty-hub-redirect-` | Loyalty Hub: the participant is sent back to the hub (entry refused, or the normal return after a play) | — |

# Errors (`onError`)

`onError` is used when the experience **can't start or can't recover**. It fires once. The error code is in `event.data.message`.

| Code | Meaning |
| ---- | ---- |
| `ERR_04` | The participation quota was already reached when the page loaded |
| `ERR_05` | Security error: the reserved `preview` parameter was used |
| `ERR_07` | The experience isn't published |
| `ERR_08` | The experience needs a premium option that isn't included in the plan |
| `ERR_09` | A `session_uid` is required but wasn't provided |
| `ERR_10` | The `session_uid` isn't allowed (not on the access list) |
| `ERR_13` | The SSO (SAML or OAuth) login failed |
| `ERR_14` | The experience isn't available in the requested language |
| `ERR_15` | The link doesn't exist (unknown or deleted experience) |
| `ERR_17` | The SSO configuration is invalid |
| `ERR_21` | A Combo™ or Dynamic Path™ has no game assigned |

`onError` can also fire without an `ERR_xx` code, for example when a screen fails to load because of a network error after the experience has started.

```
widget.onError = function (event) {
    var code = event.data && event.data.message;
    switch (code) {
        case 'ERR_09':
        case 'ERR_10':
            // session_uid missing or refused
            break;
        case 'ERR_13':
        case 'ERR_17':
            // SSO problem
            break;
        default:
            // any other fatal error
            break;
    }
};

widget.onRouteChange = function (event) {
    if (!event.data) {
        return;
    }
    if (event.data.errorCode) {
        // ERR_02, ERR_03, ERR_04, ERR_16, ERR_18 or ERR_19
    }
    if (event.data.screen.startsWith('screen_restriction-')) {
        // access refused
    }
};
```

# Session unique ID (`session_uid`)

* A `session_uid` has **no expiry date**. An unknown `session_uid` is treated as a new participant, not as an error.
* **If access control is based on `session_uid`:**
  * a value that isn't on the list gives `ERR_10` through `onError`, or the `screen_restriction-<id>-session` screen;
  * a value that has already been used gives the `screen_restriction-<id>-session_unique` screen through `onRouteChange`.
* **If participation is limited to once per session:** a reused `session_uid` gives the `screen_alreadyplayed-<id>-session` screen through `onRouteChange`.
* When SSO is enabled, the participant is identified by their SSO account and any `session_uid` passed in the URL is ignored.

| There is no "session expired" callback. Rely on the screens and codes above.

# Loading state and timeout

We don't publish guaranteed load times: they depend on the experience, its media, and the participant's device and network. To build a loading state:

1. Start a timer when you call `widget.load()`.
2. Treat the **first `onRouteChange`** as "the experience is visible" and **`onError`** as a definitive failure. Either one clears the timer.
3. Keep a timeout as a safety net. It covers the case where neither event arrives, for example when the frame itself can't load (network failure, content blocked by the browser). Measure typical load times in your own environment and set the value from those, with a generous margin for slow mobile connections.

```
var timer = setTimeout(showFallback, TIMEOUT_MS);

widget.onRouteChange = function (event) {
    clearTimeout(timer);
    hideLoader();
};

widget.onError = function (event) {
    clearTimeout(timer);
    showError(event.data && event.data.message);
};

widget.load();
```