Legacy approach. For new integrations, use the Widget SDK with Access Key Embed.
Implementation
Complete working examples in Node.js, PHP, and Python. Copy one, wire it into your auth middleware, and you're live.
Prerequisites
- A backend server (Node.js, PHP, Python, or any language with an HTTP client).
- An HTTP client library (built-in
fetch,curl, orrequests). - A user authentication system - the proxy must know who the current user is.
- Your
WIDGET_API_KEYandWIDGET_COMMUNITY_IDfrom the Returning.AI dashboard.
Complete example
Each example implements the full four-step flow: authenticate, fetch HTML, inject token, serve. Pick your language.
const express = require('express');
const app = express();
const WIDGET_BASE_URL = process.env.WIDGET_BASE_URL || 'https://prod-widgets.returning.ai';
const WIDGET_API_KEY = process.env.WIDGET_API_KEY;
const WIDGET_COMMUNITY_ID = process.env.WIDGET_COMMUNITY_ID;
const WIDGET_ID = process.env.WIDGET_ID;
const WIDGET_TYPE = process.env.WIDGET_TYPE || 'store';
const requireAuth = (req, res, next) => {
if (!req.user) return res.status(401).json({ error: 'Authentication required' });
next();
};
app.get('/widget', requireAuth, async (req, res) => {
try {
const userEmail = req.user.email;
// Step 1: Fetch JWT token
const tokenResponse = await fetch(
`${WIDGET_BASE_URL}/widget/${WIDGET_COMMUNITY_ID}/signin`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'returningai-api-key': WIDGET_API_KEY,
'email': userEmail
}
}
);
if (!tokenResponse.ok) {
return res.status(tokenResponse.status).json({ error: 'Unable to authenticate widget' });
}
const { token } = await tokenResponse.json();
// Step 2: Fetch widget HTML
const htmlResponse = await fetch(
`${WIDGET_BASE_URL}/${WIDGET_TYPE}/${WIDGET_ID}?color=light`
);
let html = await htmlResponse.text();
// Step 3: Inject token
const safeToken = token.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
html = html.replace(/token:\s*""/, `token: "${safeToken}"`);
// Step 4: Serve
res.setHeader('Content-Type', 'text/html');
res.setHeader('Cache-Control', 'no-store');
res.send(html);
} catch (error) {
console.error('Widget error:', error);
res.status(500).json({ error: 'Unable to load widget' });
}
});
app.listen(3000);Environment variables
All examples read from environment variables. Create a .env file (and add it to .gitignore):
# .env - never commit this file
WIDGET_BASE_URL=https://prod-widgets.returning.ai
WIDGET_API_KEY=rai_live_xxxxxxxxxxxxxxxxxxxx
WIDGET_COMMUNITY_ID=cm1abc2de3fg4hi5jk
WIDGET_ID=cm9xyz8wv7ut6sr5qp
WIDGET_TYPE=store| Variable | Description |
|---|---|
WIDGET_BASE_URL | Returning.AI widget host. Defaults to https://prod-widgets.returning.ai |
WIDGET_API_KEY | Your secret API key. Never expose client-side. |
WIDGET_COMMUNITY_ID | Identifies your community in the signin endpoint. |
WIDGET_ID | The specific widget to render. |
WIDGET_TYPE | Widget type slug (e.g. store, channel). |
Token injection
There are two ways to inject the JWT into the widget HTML. Use whichever matches how your widget reads its token.
Method 1: Replace in __WIDGET_INIT__
The widget HTML contains a window.__WIDGET_INIT__ object with an empty token field. Replace the empty string with the real JWT.
// Method 1: Replace __WIDGET_INIT__ token placeholder
// The widget HTML contains: window.__WIDGET_INIT__ = { token: "", ... }
html = html.replace(/token:\s*""/, `token: "${safeToken}"`);Method 2: localStorage script injection
If the widget reads its token from localStorage, inject a script that sets the token before the widget initializes.
// Method 2: Inject a localStorage script before </body>
// Use this if the widget reads its token from localStorage.
const script = `<script>localStorage.setItem('returning-ai-widget-token','${safeToken}');</script>`;
html = html.replace('</body>', script + '</body>');Cache-Control: no-store
Use no-store (not no-cache) to prevent caching of authenticated widget pages. no-cache still stores a copy and revalidates, which means a stale token could be served from a shared cache or CDN. no-store guarantees the response is never written to disk.