> Legacy approach
>
> For new integrations, use the [Widget SDK with Access Key Embed](https://docs.returning.ai/widget-sdk/auth-access-key.md).

# 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.

**Node.js**

`server.js`

```javascript
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`

```bash
# .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.

```javascript
// 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.

```javascript
// 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.
