# Hosted iframe & WebView

> Use the hosted SDK on any website or inside a native application.

Source: https://dashboard.adquest.batriders.ir/docs/embed · Audience: Developers · Section: Build

## Prerequisites

- Your platform’s Embed URL (https://sdk.adquest.batriders.ir/{publicId}).
- Your public API key.
- A backend route that creates participant sessions, and your origin on the platform’s Where AdQuest may appear list if the publisher keeps one.

## 1. Load your platform’s embed URL

Ask the publisher for the Embed URL, https://sdk.adquest.batriders.ir/{publicId}. It appears in the dashboard once the platform is approved. Use the platform UUID from the URL, not its numeric ID. The hosted runtime loads your platform branding automatically. Mint participant tokens on your backend before initializing the embed.

## 2. Connect the web message bridge

**HTML + JavaScript**

```html
<iframe id="adquest" title="AdQuest quests and rewards"
  style="width:100%;height:720px;border:0" allow="clipboard-write"></iframe>
<p id="adquest-status" role="status"></p>
<script type="module">
  const frame = document.getElementById("adquest");
  const status = document.getElementById("adquest-status");
  const origin = "https://sdk.adquest.batriders.ir";
  const publicId = "YOUR_PLATFORM_PUBLIC_ID";
  const apiKey = "YOUR_PUBLIC_API_KEY";
  const locale = "en-US"; // "en" or "fa": widget language (Persian is right-to-left)
  const send = (type, payload) => frame.contentWindow?.postMessage({ version: 1, type, payload }, origin);
  let refreshing = false;
  async function receive(event) {
    if (event.origin !== origin || event.source !== frame.contentWindow) return;
    let message;
    try { message = typeof event.data === "string" ? JSON.parse(event.data) : event.data; }
    catch { return; }
    if (message?.version !== 1) return;
    if (["adquest:ready", "adquest:authRequired"].includes(message.type)) {
      if (refreshing) return;
      refreshing = true;
      try {
        const response = await fetch("/api/adquest/session", {
          method: "POST",
          cache: "no-store",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ locale }), // your backend creates the session with the same locale
        });
        if (!response.ok) throw new Error("Sign in and reopen rewards to continue.");
        const session = await response.json();
        if (message.type === "adquest:ready") {
          send("adquest:init", { apiKey, ...session, hostContext: { locale } });
        } else {
          send("adquest:setParticipantToken", { participantToken: session.participantToken });
        }
        status.textContent = "";
      } catch (error) { status.textContent = error.message; }
      finally { refreshing = false; }
    }
    if (message.type === "adquest:error") status.textContent = message.payload?.message || "Could not load rewards.";
    if (message.type === "adquest:close") frame.hidden = true;
  }
  window.addEventListener("message", receive);
  frame.src = origin + "/" + encodeURIComponent(publicId);
  // On screen teardown: window.removeEventListener("message", receive);
</script>
```

> **Keep secrets out of the URL.** Use the same authenticated session route as the React guide. Send the participant token through the bridge, never in query parameters. Validate both message origin and iframe source. The listener is installed before frame.src so adquest:ready cannot be missed. Remove the listener when the screen is destroyed. On an account switch, recreate the iframe and initialize it with that user’s new session.

## Choose who provides the header

By default AdQuest shows its own header. If your app already has one, set navigation to "host" in adquest:init, or send adquest:setNavigation later. AdQuest then hides its header and shows a Back to platform button on the home page. Inner pages keep their own Back button. When the user presses Back to platform, the embed sends adquest:exit. Close or leave the screen in response.

**Provide your own header**

```javascript
send("adquest:init", { apiKey, ...session, navigation: "host" });

if (message.type === "adquest:exit") frame.hidden = true;
```

## Choose the language

Set hostContext.locale in adquest:init. It is part of hostContext, not a top-level init field. Use the same value when your backend creates the participant session.

**Set and change the language**

```javascript
// In adquest:init
send("adquest:init", { apiKey, ...session, hostContext: { locale: "en-US" } });

// Later, when the user changes language: new session first, then
send("adquest:setParticipantToken", { participantToken: newSession.participantToken });
send("adquest:setHostContext", { locale: "en-US" });
```

- [Language and country](https://dashboard.adquest.batriders.ir/docs/participant-sessions#language): Supported values, what the locale controls and how to change it.

## Show the start splash

The start splash is a short animation that covers the embed while it loads, so a visitor never sees an empty frame or a spinner. It runs in your page, outside the iframe, which is why your page has to turn it on: with one script tag, or with one call if your app already bundles @adquest/sdk. Without either, nothing changes: the embed loads as before.

**Script tag**

```html
<script async src="https://sdk.adquest.batriders.ir/loader.js"
  data-splash='{"preset":"black-friday","color":"#ff5a1f","title":"MEGA FRIDAY"}'></script>
```

**Or from the SDK**

```javascript
import { enableSplash } from "@adquest/sdk";

// once, when your app starts (it does nothing on the server)
enableSplash({ preset: "black-friday", color: "#ff5a1f", title: "MEGA FRIDAY" });
```

| Setting | Value | Default |
| --- | --- | --- |
| preset | "vault", "coins", "black-friday" or "none" | "vault" |
| color | Your brand colour as #rgb or #rrggbb | Gold |
| title | Headline of the black-friday splash, up to 24 characters | BLACK FRIDAY |
| tagline | Second line and moving tape of the black-friday splash, up to 60 characters | The title |
| dir | "rtl" or "ltr". Sets the text direction and the side the progress fills from. Nothing changes position | The wording’s own direction (black-friday), otherwise your page’s |

- Every setting is optional. A bare tag, or enableSplash() with no arguments, plays the default splash. preset "none" plays nothing.
- The settings live in your own code and nowhere else: nothing is stored on AdQuest’s side and nothing is requested for them, so even a first-time visitor sees exactly what you set. To change the splash, change the code.
- Add it once, in the head of the page or at app start, before the visitor reaches the AdQuest screen. Keep your iframe and bridge code as they are: the loader finds AdQuest iframes by their address.
- The loader is about 7 KB, has no dependencies and never calls the AdQuest API.
- A visitor sees the full animation once per browser session and a short fade after that. Visitors who ask their device for reduced motion get a still cover.
- The cover leaves when the embed sends adquest:contentReady, or after 10 seconds at the latest.

**Optional: connect early**

```javascript
// e.g. when the visitor presses the button that opens AdQuest
openButton.addEventListener("pointerdown", () => window.AdQuestSplash?.warm());
```

> **Native WebView.** A WebView has no host page for the script to run in. Show your own native loading view and hide it when you receive adquest:contentReady.

## 3. Connect a native WebView

1. Load the same HTTPS embed URL and enable JavaScript for that trusted page.
2. Register the outgoing native bridge before the page loads. Decode version 1 envelopes and wait for adquest:ready.
3. Send adquest:init to window.AdQuestEmbed.receive(envelope) after you have that user’s participant session. Put the language in payload.hostContext.locale (en or fa). Build the envelope as data, then JSON-serialize it. Do not concatenate tokens into JavaScript.
4. Handle adquest:authRequired by refreshing through your backend. Send adquest:setParticipantToken with the new token.
5. Handle adquest:navigation, adquest:goBack and adquest:close so the native back control returns within the widget before dismissing it.
6. If your app provides the header, add navigation: "host" to the adquest:init payload (or send adquest:setNavigation). The widget hides its header and shows Back to platform on the home page. Dismiss the WebView when you receive adquest:exit.
7. Handle adquest:openUrl using your native URL policy. Allow intended HTTPS destinations and supported deep links; do not allow arbitrary schemes or untrusted navigations to access the bridge.

| Host | Receive messages from embed |
| --- | --- |
| Android / generic native bridge | AdQuestBridge.postMessage(JSON string) |
| iOS WKWebView | window.webkit.messageHandlers.adquest.postMessage(envelope) |
| React Native WebView | window.ReactNativeWebView.postMessage(JSON string) |
| Flutter InAppWebView | flutter_inappwebview.callHandler('adquest', envelope) |

## Bridge message reference

- [Bridge messages](https://dashboard.adquest.batriders.ir/docs/bridge-messages): Every message between your page or app and the embed.

## Next steps

- [Bridge messages](https://dashboard.adquest.batriders.ir/docs/bridge-messages): Every message between your page and the embed.
- [Participant sessions](https://dashboard.adquest.batriders.ir/docs/participant-sessions): Create and refresh the token.
- [Test & go live](https://dashboard.adquest.batriders.ir/docs/launch): Check the full journey before launch.
