# Banners

A separate, lightweight runtime for embedding Returning.AI banners on any page without an iframe. An open banner configured for anonymous visitors needs no token; gated banners follow the access rules below. Available since SDK 1.6.0 as its own bundle, `rai-banner.iife.js`.

> Custom widgets render eligible banners automatically
>
> Since SDK 1.6.0, mounting `<rai-custom-widget>` also renders banners targeted at that configured experience, and the widget element emits the same `rai-banner-*` events. Disable per widget with `banner="off"` (or the `no-banner` attribute). The standalone `<rai-banner>` element below is for pages with no widget.

## Embed

Ask your setup contact for the community ID and an enabled banner for anonymous visitors. These examples explicitly choose the production host with `domain-key="GEN"`; the standalone banner runtime otherwise defaults to localhost. For another environment, use the supplied key or `api-url`. Your CMS must permit the script.

Custom element:

```html
<script src="https://unpkg.com/@returningai/widget-sdk@1.8.11/dist/rai-banner.iife.js"></script>

<rai-banner community-id="YOUR_COMMUNITY_ID" domain-key="GEN"></rai-banner>
```

Or script-tag bootstrap (auto-creates the element):

```html
<script
  src="https://unpkg.com/@returningai/widget-sdk@1.8.11/dist/rai-banner.iife.js"
  data-rai-banner
  data-community-id="YOUR_COMMUNITY_ID"
  data-domain-key="GEN"
></script>
```

No mount `<div>` is required for any placement. Overlay and popup banners float over the page as a centered card; inline banners render into the banner's configured target selector, or in place where the element sits when that selector is not on the page.

## Attributes

Attributes work with or without the `data-` prefix, on the element or the bootstrap script tag.

| Attribute | Required | Description |
| --- | --- | --- |
| `community-id` | Yes | Your community ID from Community Settings. |
| `api-url` | No | Explicit API base URL (overrides `domain-key`). |
| `domain-key` | No | Key resolving the API host. Defaults to `LOCAL`, so production embeds should set the key Returning.AI gives them (or an explicit `api-url`). |
| `surface` | No | Eligibility surface. Defaults to `sdk`; leave it unless Returning.AI tells you otherwise. |
| `page-path / page-hostname / page-url` | No | Override the page context used for banner page rules. Defaults to `window.location`. |
| `storage-prefix` | No | localStorage key prefix for the anonymous visitor ID. |
| `embed-token` | No* | SDK embed token minted from your access ID/key, exactly like [Access Key Embed](https://docs.returning.ai/widget-sdk/auth-access-key.md) for widgets. **Required for SDK-auth banners.** A domain banner can instead use its per-banner allowed-domain rule; the token is an alternative when its access key authorizes the request origin. See Access types below. |

```html
<!-- Use this instead of the open-banner example.
     Mint the embed token on your trusted server. -->
<script src="https://unpkg.com/@returningai/widget-sdk@1.8.11/dist/rai-banner.iife.js"></script>
<rai-banner
  community-id="YOUR_COMMUNITY_ID"
  domain-key="GEN"
  embed-token="EMBED_TOKEN_FROM_YOUR_SERVER"
></rai-banner>
```

## How it works

1. Mints or loads an anonymous visitor UUID in localStorage (`anonymous_session`), enabling frequency caps and dismiss-memory without a signed-in user.
2. Calls the public eligibility endpoint `POST /v2/api/banner-sdk/eligible` with page and viewport context.
3. Renders the returned banner: overlay/popup placements as a centered card, inline into the configured target (or in place).
4. Fires one-time, HMAC-signed impression / click / dismiss tracking URLs (replay-protected server-side).
5. Banner HTML is sanitized client-side with DOMPurify before injection.

### Access types

Each banner has an Access setting (chosen in the dashboard). The rules below describe the current service implementation. Have your setup owner confirm the effective per-banner rule in your target environment before relying on tokenless domain access.

| Banner Access | What the embed needs |
| --- | --- |
| Open to everyone | No token for a banner configured to allow anonymous visitors. Supply the community ID and target environment; its targeting and eligibility rules still apply. |
| Domain gate | The browser request hostname passes the per-banner allowed-domain rule (from Origin, or Referer when Origin is absent), *or* an `embed-token` whose access key authorizes the origin. |
| SDK auth | A valid `embed-token` whose access key authorizes the origin. |

Widget-mounted banners pass the widget's `embed-token` automatically. The banner's access, origin and targeting rules still have to pass; token forwarding alone does not enable a banner. A live banner whose surfaces include `sdk` is required.

## Events

The element emits bubbling `CustomEvent`s. Widget-mounted banners emit the same events from the widget element.

| Event | Detail |
| --- | --- |
| `rai-banner-impression` | `{ bannerId }` |
| `rai-banner-click` | `{ bannerId }` |
| `rai-banner-dismiss` | `{ bannerId }` |
| `rai-banner-empty` | `{ reason }` - no eligible banner |
| `rai-banner-error` | `{ error }` |

```javascript
document.addEventListener("rai-banner-impression", (e) =>
  console.log("banner shown", e.detail.bannerId),
);
document.addEventListener("rai-banner-click", (e) =>
  console.log("banner clicked", e.detail.bannerId),
);
document.addEventListener("rai-banner-dismiss", (e) =>
  console.log("banner dismissed", e.detail.bannerId),
);
document.addEventListener("rai-banner-empty", (e) =>
  console.log("no eligible banner", e.detail.reason),
);
document.addEventListener("rai-banner-error", (e) =>
  console.error("banner error", e.detail.error),
);
```

Note: since 1.6.0 the main widget bundle also makes one eligibility request per widget mount and bundles DOMPurify, so `rai-widget.iife.js` is slightly larger. Use `banner="off"` on a widget to skip that request.
