Skip to content

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, or requests).
  • A user authentication system - the proxy must know who the current user is.
  • Your WIDGET_API_KEY and WIDGET_COMMUNITY_ID from the Returning.AI dashboard.

Complete example

Each example implements the full four-step flow: authenticate, fetch HTML, inject token, serve. Pick your language.

server.js
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
# .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
VariableDescription
WIDGET_BASE_URLReturning.AI widget host. Defaults to https://prod-widgets.returning.ai
WIDGET_API_KEYYour secret API key. Never expose client-side.
WIDGET_COMMUNITY_IDIdentifies your community in the signin endpoint.
WIDGET_IDThe specific widget to render.
WIDGET_TYPEWidget 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>');