Auth - Access Key
The recommended way to authenticate widgets. Your backend exchanges access credentials for a short-lived JWT, which is injected into the widget tag as an HTML attribute. No persistent auth endpoint needed.
When to use
- Logged-in portals and trader dashboards where you know the user server-side.
- Any integration where server-verified identity is important.
- Bundle mode (native DOM rendering) for the best performance and CSS integration.
Step 1 - Get access credentials
In the Returning.AI admin panel, generate an accessId and accessKey pair. Store them in your server's environment variables - they must never be exposed to the client.
Keep credentials server-side
Never include RAI_ACCESS_ID or RAI_ACCESS_KEY in client-side code. They should only exist in environment variables on your backend.
Step 2 - Create a backend endpoint
Your server calls the Access Key API with your accessId and accessKey. The response returns a short-lived embed token at data.embedToken. End-user identity is still provided separately on the widget element via configured data-* attributes.
app.get('/api/widget-token', async (req, res) => {
const response = await fetch(
'https://api-v2.returning.ai/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,
}),
}
)
const json = await response.json()
const embedToken = json.data?.embedToken
if (!embedToken) throw new Error('Missing embed token')
res.json({ embedToken })
})Step 3 - Inject the token into the page
Your server renders the widget tag with the embed-token attribute set to the token from Step 2. The SDK validates that token in the browser as an access gate, then continues startup using the configured user identifiers on the widget element.
Step 4 - Add the widget
Add the widget tag with embed-token and bundle-url attributes. The bundle-url enables bundle mode (recommended) where the widget renders directly in the page DOM instead of an iframe.
<rai-store-widget
community-id="YOUR_COMMUNITY_ID"
api-url="https://prod-widgets.returning.ai"
v2-api-url="https://api-v2.returning.ai"
embed-token="TOKEN_FROM_YOUR_SERVER"
domain-key="PROD"
data-user-id="USER_ID"
data-user-objectid="USER_OBJECT_ID"
data-username="USERNAME"
bundle-url="https://prod-widgets.returning.ai/store-widget/bundle/widget.js"
theme="dark"
width="100%"
height="600px"
></rai-store-widget>Bundle mode
When bundle-url is set, the widget renders in the light DOM (no iframe). Your page CSS cascades into the widget, scrolling is native, and performance is better. Omit bundle-url to fall back to iframe mode if you need CSS isolation.
Token lifecycle
Access Key tokens are short-lived JWTs that expire after 15 minutes. The SDK does not call your backend to mint a fresh embed-token automatically. For long-lived views, your app must fetch a new embed token, update the attribute, and reload the widget.
| Scenario | Strategy |
|---|---|
| Server-rendered pages | Token generated per page load. 15 minutes is plenty for most page sessions. |
| SPAs | Re-fetch the token from your backend on route navigation. Each view gets a fresh token. |
| Long-lived sessions (>15 min on one view) | Listen for the rai-error event, re-fetch a token from your backend, update the embed-token attribute, and call window.ReturningAIWidget.reload(). |
Handling token expiry in long-lived sessions
If a user stays on the same page for more than 15 minutes, the token will expire. Use this pattern to recover automatically:
const widget = document.querySelector('rai-store-widget')
widget.addEventListener('rai-error', async (e) => {
console.warn('Widget token expired, refreshing...')
// Re-fetch a fresh token from your backend
const res = await fetch('/api/widget-token')
const { token } = await res.json()
// Update the attribute and reload
widget.setAttribute('embed-token', token)
await window.ReturningAIWidget.reload()
})