@returningai/widget-sdk
v1.4.0
Published
Shadow DOM isolated widget SDK for ReturningAI
Downloads
580
Readme
@returningai/widget-sdk
Embed ReturningAI widgets on any website — React, Vue, Angular, or plain <script> tag. No external dependencies.
Quick Start
Your access credentials stay on your server. Your server exchanges them for a short-lived embed token (15 min) and injects only that token into the page. No secret ever reaches the browser.
Step 1 — Create an access key pair (one-time, from your dashboard):
Go to Community Settings > Integrations > SDK Access and click Create SDK Key. Enter a name and optional allowed origins (required in production; localhost is auto-allowed in dev).
The dashboard will display your accessId and accessKey. The accessKey is shown once only — save it securely.
Store both values in your server environment variables.
Rotating or revoking keys: The same dashboard page lists existing keys and lets you revoke or replace them. Revocation takes effect immediately for token issuance, and forces active sessions to re-auth on their next page load.
Step 2 — Exchange credentials for an embed token on every page load (Node.js example):
// Runs on YOUR SERVER — never in the browser
const response = await fetch('https://YOUR_API_URL/v2/api/widget-access-keys/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
accessId: process.env.RAI_ACCESS_ID,
accessKey: process.env.RAI_ACCESS_KEY,
// userIdentifiers keys MUST be `data-*` prefixed — they match the
// `dataAttribute` value of each field configured in your widget's
// User Identifier Fields (e.g. the dashboard stores "data-email",
// "data-user-id", not bare "email"/"userId").
userIdentifiers: {
'data-user-id': [YourUserObject].id,
'data-email': [YourUserObject].email,
// ...any other fields configured for this widget
},
}),
})
const { data } = await response.json()
// data.embedToken expires in 15 minutes and carries signed identity claimsStep 3 — Embed (only the short-lived token appears in HTML — no identity attributes):
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
data-widget-id="YOUR_WIDGET_ID"
data-widget-type="store"
data-theme="dark"
data-embed-token="eyJ..."
widget-url="YOUR_WIDGET_URL"
></script>api-url is optional. The SDK resolves the widget server from domain-key when provided and uses production by default; pass api-url only if you need to override the widget auth server.
The server validates the token and extracts identity from its signed claims. If the token is expired, invalid, or its identifiers don't satisfy the widget's configured User Identifier Fields, the widget shows an error screen. Tokens expire after 15 minutes — regenerate on each page render.
Install
CDN (no install needed)
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>npm / yarn / pnpm
npm install @returningai/widget-sdkWidget Types
Set the data-widget-type attribute to choose which widget to display. The Widget ID and Widget URL for each type are available in your dashboard.
| data-widget-type | Description |
|---------------------|-------------|
| store | Store / rewards widget |
| channel | Channel widget |
| milestone | Milestone widget |
| social | Social widget |
| currency-view | Currency widget |
| referral-conditions | Referral widget |
| custom | Custom widget |
All examples below use Access Key Embed (recommended) — identity is signed into data-embed-token server-side per the Quick Start. Swap data-embed-token="eyJ..." for data-* identifier attributes if you want Public Embed instead.
Store widget
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
data-widget-id="YOUR_WIDGET_ID"
data-widget-type="store"
data-theme="dark"
data-embed-token="eyJ..."
widget-url="YOUR_WIDGET_URL"
></script>Channel widget
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
data-widget-id="YOUR_WIDGET_ID"
data-widget-type="channel"
data-theme="dark"
data-embed-token="eyJ..."
widget-url="YOUR_WIDGET_URL"
></script>Note:
YOUR_WIDGET_IDfor channel widgets is the base64-encoded channel ID — find it under Community Settings > Channels in your dashboard. Alternatively, use the custom element approach with separatecommunity-idandchannel-idattributes.
Social widget
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
data-widget-id="YOUR_WIDGET_ID"
data-widget-type="social"
data-theme="dark"
data-embed-token="eyJ..."
widget-url="YOUR_WIDGET_URL"
></script>Milestone widget
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
data-widget-id="YOUR_WIDGET_ID"
data-widget-type="milestone"
data-theme="dark"
data-embed-token="eyJ..."
widget-url="YOUR_WIDGET_URL"
></script>Custom widget
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
data-widget-id="YOUR_WIDGET_ID"
data-widget-type="custom"
data-theme="dark"
data-embed-token="eyJ..."
widget-url="YOUR_WIDGET_URL"
></script>Custom widgets:
YOUR_WIDGET_IDis the base64-encoded_idof the CustomWidget record (not the community ID). The custom element variant<rai-custom-widget widget-id="...">uses this same base64 value — it's the one widget type where raw ObjectIds are not accepted.
Attributes
There are two ways to identify your widget:
- Script-tag embed: Use
data-widget-id(a pre-encoded ID from your dashboard) on the<script>tag - Custom element embed: Use
community-id(andchannel-idfor channel widgets) on the custom element tag
All attributes can be provided with or without the data- prefix — both work.
Script-tag attributes
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| data-widget-id | Yes | — | Your widget ID (from dashboard) |
| data-widget-type | Yes | store | Widget type (see table above) |
| api-url | No | Production widget server | Optional widget auth server override. Defaults from domain-key when provided |
| widget-url | Yes* | — | Widget URL (from dashboard). *Not required when using data-bundle-url |
| data-bundle-url | No | — | URL to the widget IIFE bundle — triggers bundle mode (non-iframe) |
| data-domain-key | No | PROD | Environment key: SGTR, STG, or PROD. Used to resolve default environment URLs when overrides are omitted |
| data-theme | No | light | light or dark |
| data-email | Public Embed only | — | User's email for identification. Access Key Embed: omit — identity is signed into data-embed-token |
| data-embed-token | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no data-* identifier attributes needed |
| data-locale | No | — | BCP 47 locale tag (e.g. fr-FR) |
| data-eager | No | — | Load immediately instead of waiting until visible (presence-based boolean) |
| data-auto-refresh | No | true | Auto-refresh access token before expiry |
| data-debug | No | false | Verbose console logging |
| data-custom-data | No | — | JSON object forwarded to the widget |
| data-retry-label | No | Retry | Label text for the retry button on the error screen |
| data-v2-api-url | No | Domain-key default | Optional V2 API override for bundle mode. Safe to omit for standard environments |
Custom element attributes
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| community-id | Yes* | — | Your community ID (raw ObjectId from dashboard). *Custom widgets use widget-id instead |
| channel-id | Channel only | — | Channel ID (raw ObjectId) — required for <rai-channel-widget> |
| api-url | No | Production widget server | Optional widget auth server override. Defaults from domain-key when provided |
| widget-url | Yes* | — | Widget URL (from dashboard). *Not required when using bundle-url |
| bundle-url | No | — | URL to the widget IIFE bundle — triggers bundle mode (non-iframe) |
| domain-key | No | PROD | Environment key: SGTR, STG, or PROD. Used to resolve default environment URLs when overrides are omitted |
| theme | No | light | light or dark |
| data-email | Public Embed only | — | User's email for identification. Access Key Embed: omit — identity is signed into embed-token |
| embed-token | Access Key Embed only | — | Short-lived JWT from your server. Carries signed user identifiers — no data-* identifier attributes needed |
| width | No | 100% | CSS width |
| height | No | 600px | CSS height |
| locale | No | — | BCP 47 locale tag (e.g. fr-FR) |
| eager | No | — | Load immediately instead of waiting until visible (presence-based boolean) |
| auto-refresh | No | true | Auto-refresh access token before expiry |
| debug | No | false | Verbose console logging |
| custom-data | No | — | JSON object forwarded to the widget |
| retry-label | No | Retry | Label text for the retry button on the error screen |
| v2-api-url | No | Domain-key default | Optional V2 API override for bundle mode. Safe to omit for standard environments |
widget-idvscommunity-id: The script-tag method usesdata-widget-idwhich is a pre-encoded identifier (base64). The custom element method uses raw ObjectIds —community-idfor most widget types, pluschannel-idfor channel widgets. The SDK handles the encoding internally.
Container <div> (script-tag method only)
The SDK looks for a <div> with id="returning-ai-widget-{YOUR_WIDGET_ID}". Size the div to control the widget dimensions:
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:600px"></div>Custom elements don't need a container div — they size themselves using width and height attributes (defaults: 100% and 600px).
User Identifier Attributes
Access Key Embed: Do not put identifiers in HTML. Pass them inside the
userIdentifiersobject when exchanging your access credentials for an embed token (Step 2). Identity is cryptographically signed into the token so it cannot be tampered with from the browser.Public Embed: Pass any
data-*attribute to identify the current user. Which identifiers are available is configured per community in your dashboard.
<!-- Public Embed — identifiers in HTML -->
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
data-widget-id="YOUR_WIDGET_ID"
data-widget-type="store"
data-email="[email protected]"
data-user-id="12345"
data-account-id="abc"
widget-url="YOUR_WIDGET_URL"
></script>In Public Embed mode, any data-* attribute not reserved by the SDK is forwarded to the auth API as a user identifier.
Bundle Mode
When you set the bundle-url attribute, the widget renders directly in the page DOM instead of inside an iframe. This gives full CSS cascade — html[data-theme] and Tailwind classes apply to the widget content naturally.
widget-urlis not needed in bundle mode (the widget is loaded directly frombundle-url).api-urlis optional. The SDK resolves the widget auth server fromdomain-keywhen provided, or defaults to production.domain-keyis optional. When omitted, the SDK defaults to production (PROD).- Environment URL variables such as
window.api_urlare optional overrides. StandardSGTR,STG, andPRODenvironments resolve fromdomain-key.
Both examples below use Access Key Embed and target staging. Omit domain-key for production.
<!-- Script-tag approach -->
<div id="returning-ai-widget-YOUR_WIDGET_ID" style="width:100%;height:100vh"></div>
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"
data-widget-id="YOUR_WIDGET_ID"
data-widget-type="store"
data-theme="dark"
data-embed-token="eyJ..."
data-bundle-url="YOUR_BUNDLE_URL"
data-domain-key="STG"
data-eager
></script>
<!-- Custom element approach -->
<rai-store-widget
community-id="YOUR_COMMUNITY_ID"
bundle-url="YOUR_BUNDLE_URL"
domain-key="STG"
embed-token="eyJ..."
theme="dark"
width="100%"
height="100vh"
eager
></rai-store-widget>Each widget type has its own IIFE bundle and global name:
| Widget type | Bundle global | Custom element tag |
|-------------|--------------|-------------------|
| store | RaiStoreWidget | <rai-store-widget> |
| channel | RaiChannelWidget | <rai-channel-widget> |
| social | RaiSocialWidget | <rai-social-widget> |
| milestone | RaiMilestoneWidget | <rai-milestone-widget> |
| currency-view | RaiCurrencyWidget | <rai-currency-widget> |
| referral-conditions | RaiReferralWidget | <rai-referral-widget> |
| custom | RaiCustomWidget | <rai-custom-widget> |
The bundle must export a mount(container, config) function on its global (e.g. window.RaiStoreWidget.mount).
Framework Usage (Custom Elements)
When using a frontend framework (React, Vue, Angular), you can use custom HTML element tags instead of the script-tag method. Custom elements use raw ObjectIds (community-id, channel-id) instead of the pre-encoded data-widget-id — the SDK handles the encoding internally. (Exception: <rai-custom-widget> uses widget-id with a base64-encoded value — see the attributes table.)
All examples below use Access Key Embed — pass the short-lived token your server minted into embed-token. For Public Embed, replace embed-token with data-* identifier attributes.
Plain HTML
<script src="https://unpkg.com/@returningai/widget-sdk/dist/rai-widget.iife.js"></script>
<!-- Store widget -->
<rai-store-widget
community-id="YOUR_COMMUNITY_ID"
theme="dark"
embed-token="eyJ..."
widget-url="YOUR_WIDGET_URL"
></rai-store-widget>
<!-- Channel widget — requires both community-id AND channel-id -->
<rai-channel-widget
community-id="YOUR_COMMUNITY_ID"
channel-id="YOUR_CHANNEL_ID"
theme="dark"
embed-token="eyJ..."
widget-url="YOUR_WIDGET_URL"
></rai-channel-widget>React (TypeScript)
Types are included — no manual JSX declarations needed.
import '@returningai/widget-sdk'
export function StoreEmbed({ embedToken }: { embedToken: string }) {
return (
<rai-store-widget
community-id="YOUR_COMMUNITY_ID"
theme="dark"
embed-token={embedToken}
widget-url="YOUR_WIDGET_URL"
/>
)
}
export function ChannelEmbed({ embedToken }: { embedToken: string }) {
return (
<rai-channel-widget
community-id="YOUR_COMMUNITY_ID"
channel-id="YOUR_CHANNEL_ID"
theme="dark"
embed-token={embedToken}
widget-url="YOUR_WIDGET_URL"
/>
)
}Vue 3
<script setup>
import '@returningai/widget-sdk'
defineProps({ embedToken: String })
</script>
<template>
<!-- Store widget -->
<rai-store-widget
community-id="YOUR_COMMUNITY_ID"
theme="dark"
:embed-token="embedToken"
widget-url="YOUR_WIDGET_URL"
/>
<!-- Channel widget -->
<rai-channel-widget
community-id="YOUR_COMMUNITY_ID"
channel-id="YOUR_CHANNEL_ID"
theme="dark"
:embed-token="embedToken"
widget-url="YOUR_WIDGET_URL"
/>
</template>Angular
// app.module.ts
import '@returningai/widget-sdk'
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
@NgModule({ schemas: [CUSTOM_ELEMENTS_SCHEMA] })<!-- Store widget -->
<rai-store-widget
community-id="YOUR_COMMUNITY_ID"
theme="dark"
[attr.embed-token]="embedToken"
widget-url="YOUR_WIDGET_URL"
></rai-store-widget>
<!-- Channel widget -->
<rai-channel-widget
community-id="YOUR_COMMUNITY_ID"
channel-id="YOUR_CHANNEL_ID"
theme="dark"
[attr.embed-token]="embedToken"
widget-url="YOUR_WIDGET_URL"
></rai-channel-widget>Available custom element tags
| Tag | Required attributes | Widget type |
|-----|-------------------|-------------|
| <rai-store-widget> | community-id | Store |
| <rai-channel-widget> | community-id + channel-id | Channel |
| <rai-milestone-widget> | community-id | Milestone |
| <rai-social-widget> | community-id | Social |
| <rai-currency-widget> | community-id | Currency |
| <rai-referral-widget> | community-id | Referral |
| <rai-custom-widget> | widget-id (not community-id) | Custom |
DOM Events
Listen for widget lifecycle events:
| Event | detail | Fired when |
|-------|----------|-----------|
| rai-authenticated | {} | Auth succeeded, before widget mounts |
| rai-ready | {} | WIDGET_READY received, loader hidden (iframe mode) |
| rai-mounted | {} | Widget bundle mounted successfully (bundle mode) |
| rai-error | { message, hint?, status? } | Auth failed after all retries. message is the backend error text (e.g. "Invalid or expired embed token"), hint is the optional human-readable suggestion, status is the HTTP status code of the failing response. |
| rai-logout | {} | Widget logged out |
| rai-height-change | { height } | iframe resized (after debounce) |
// Script-tag embed
const widget = document.querySelector('[data-widget-id]')
// Custom element embed
const widget = document.querySelector('rai-store-widget')
widget.addEventListener('rai-authenticated', () => { /* auth succeeded */ })
widget.addEventListener('rai-ready', () => { /* widget loaded and visible (iframe mode) */ })
widget.addEventListener('rai-mounted', () => { /* widget mounted (bundle mode) */ })
widget.addEventListener('rai-error', (e) => console.error(e.detail.status, e.detail.message, e.detail.hint))
widget.addEventListener('rai-logout', () => { /* user logged out */ })
widget.addEventListener('rai-height-change', (e) => console.log(e.detail.height))Public API
After the SDK loads, window.ReturningAIWidget is available:
window.ReturningAIWidget.version // current SDK version
await window.ReturningAIWidget.reload() // re-runs auth flow and reloads widget
await window.ReturningAIWidget.logout() // clears tokens and removes widget
window.ReturningAIWidget.isAuthenticated() // boolean
window.ReturningAIWidget.getTokenInfo() // token metadata (no token values exposed)Browser Support
Requires Custom Elements v1 + Shadow DOM v1.
| Browser | Minimum version | |---------|----------------| | Chrome | 67 | | Firefox | 63 | | Safari | 13 |
