Skip to content
Returning.AIDocs

Referral Widget

Embed the Returning.AI referral experience in a logged-in portal using the Widget SDK, Access Key Embed, and the <rai-referral-widget> web component.

Flow

  1. 1. Register the user. Your backend sends a registration event to Returning.AI after signup or account confirmation. Use the handshake URL first, then send the main webhook request with x-session-token.
  2. 2. Mint the widget token. Your backend exchanges accessId/accessKey for a short-lived embed-token. Put the user identifier inside userIdentifiers, not on the widget tag.
  3. 3. Render the widget. The frontend renders <rai-referral-widget> with the returned token and the referral-conditions-widget bundle URL.

Register Users Server-Side

Registration events belong on your backend. Use the workflow API key only on the handshake request. The main webhook call should use the returned sessionToken in the x-session-token header.

registration-webhook.js
async function sendRegistrationToReturningAI(user) {
  const handshakeResponse = await fetch(
    process.env.RAI_REGISTRATION_HANDSHAKE_URL,
    {
      method: 'POST',
      headers: {
        Authorization: process.env.RAI_REGISTRATION_WEBHOOK_API_KEY,
        'Content-Type': 'application/json',
      },
    }
  )

  if (!handshakeResponse.ok) {
    throw new Error('Returning.AI registration handshake failed')
  }

  const handshakeBody = await handshakeResponse.json()
  const sessionToken = handshakeBody.data?.sessionToken
  if (!sessionToken) {
    throw new Error('Returning.AI registration handshake returned no session token')
  }

  const webhookResponse = await fetch(
    process.env.RAI_REGISTRATION_WEBHOOK_URL,
    {
      method: 'POST',
      headers: {
        'x-session-token': sessionToken,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        customerId: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        registeredAt: new Date().toISOString(),
      }),
    }
  )

  if (!webhookResponse.ok) {
    throw new Error('Returning.AI registration webhook failed')
  }
}

Mint the Referral Embed Token

The access key stays server-side. Prefer a stable broker-owned ID such as data-customer-id over email unless email is your canonical mapping key.

server.js
app.get('/api/returningai/referral-token', async (req, res) => {
  const user = req.user

  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,
        userIdentifiers: {
          'data-customer-id': user.id,
        },
      }),
    }
  )

  if (!response.ok) {
    return res.status(502).json({ error: 'Failed to mint embed token' })
  }

  const result = await response.json()
  res.json({ embedToken: result.data.embedToken })
})

Render the Widget

Use embed-token, not data-embed-token. Use data-embed-token only on SDK script-tag embeds. Do not put data-customer-id or data-email directly on the widget for Access Key Embed.

referral-widget.html
<rai-referral-widget
  community-id="YOUR_COMMUNITY_ID"
  embed-token="TOKEN_FROM_YOUR_BACKEND"
  bundle-url="https://prod-widgets.returning.ai/referral-conditions-widget/bundle/widget.js"
  theme="dark"
  width="100%"
  height="600px"
></rai-referral-widget>

React example

ReferralPage.tsx
import { useEffect, useRef, useState } from 'react'
import '@returningai/widget-sdk'

export function ReferralPage() {
  const widgetRef = useRef<HTMLElement | null>(null)
  const [embedToken, setEmbedToken] = useState<string | null>(null)

  async function loadEmbedToken() {
    const response = await fetch('/api/returningai/referral-token')
    if (!response.ok) throw new Error('Failed to load Returning.AI token')

    const result = await response.json()
    setEmbedToken(result.embedToken)
    return result.embedToken
  }

  useEffect(() => {
    loadEmbedToken().catch(console.error)
  }, [])

  useEffect(() => {
    const widget = widgetRef.current
    if (!widget) return

    const handleError = async () => {
      const freshToken = await loadEmbedToken()
      widget.setAttribute('embed-token', freshToken)
      await window.ReturningAIWidget?.reload?.()
    }

    widget.addEventListener('rai-error', handleError)
    return () => widget.removeEventListener('rai-error', handleError)
  }, [])

  if (!embedToken) return null

  return (
    <rai-referral-widget
      ref={widgetRef}
      community-id="YOUR_COMMUNITY_ID"
      embed-token={embedToken}
      bundle-url="https://prod-widgets.returning.ai/referral-conditions-widget/bundle/widget.js"
      theme="dark"
      width="100%"
      height="600px"
    />
  )
}

TypeScript JSX Declaration

If TypeScript does not recognize the custom element, add a declaration file such as src/types/returningai-widget.d.ts.

returningai-widget.d.ts
import type * as React from 'react'

type ReturningAIReferralWidgetProps =
  React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
    'community-id'?: string
    'embed-token'?: string
    'bundle-url'?: string
    theme?: string
    width?: string
    height?: string
  }

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'rai-referral-widget': ReturningAIReferralWidgetProps
    }
  }
}

export {}

Checklist

  • Use the same identifier in registration and in the token mint request.
  • Keep access keys and workflow API keys on the backend only.
  • Use the registration handshake before calling the main webhook.
  • Use the slug referral-conditions-widget in the bundle URL.
  • Refresh long-lived widget sessions on the rai-error event.

For the generic Access Key flow, see Auth - Access Key.