Skip to content

Embedding

Embed your Ragtime assistant into any website or application using a simple iframe. Pass data via query parameters or POST body, and optionally secure requests with HMAC signatures.

The simplest way to embed your assistant is a standard iframe. Replace the URL with your assistant’s live URL (found in Dashboard → Assistant → Deployment):

<iframe
src="https://your-ragtime-instance.com/chat/org-slug/assistant-slug"
width="100%"
height="700"
style="border: none; border-radius: 12px;"
allow="microphone; geolocation"
></iframe>

If the assistant has Require GPS location enabled in the dashboard, the embedded chat will request browser location access and will not proceed without the user’s permission. In that mode, make sure the host page allows geolocation in the iframe.

When geospatial retrieval is enabled for an assistant, the chat client includes the user’s current location in each request automatically. The behavior is controlled by the assistant’s dashboard settings, so location-aware parameters are ignored unless Require GPS location is turned on.

If you are rendering Ragtime inside your own iframe wrapper, include geolocation in the iframe permissions and expect the browser to prompt the end user for access.

If you’ve configured External Variables (Dashboard → Assistant → Deployment → External Variables) with URL Query source, append them as query parameters:

<!-- Pass data via URL query parameters -->
<iframe
src="https://your-ragtime-instance.com/chat/org-slug/assistant-slug?user_name=John&locale=en"
width="100%"
height="700"
style="border: none; border-radius: 12px;"
allow="microphone"
name="ragtime-chat"
></iframe>

To keep values out of the URL, use POST Body variables. Create a hidden form that targets the iframe by name. (A POST hides values from the address bar; it does not prove they came from you. For that, turn on signing.)

<!-- When using POST body parameters, submit via a form targeting the iframe -->
<iframe
name="ragtime-chat"
width="100%"
height="700"
style="border: none; border-radius: 12px;"
allow="microphone"
></iframe>
<form
method="POST"
action="https://your-ragtime-instance.com/chat/org-slug/assistant-slug"
target="ragtime-chat"
style="display:none;"
>
<input type="hidden" name="user_id" value="abc123" />
<input type="hidden" name="session_token" value="..." />
</form>

Each external variable has a type, set in the Type column next to it:

Type Accepts
Text Up to 200 characters (or a lower limit you set), on one line
Number A number, optionally within a minimum and maximum
Yes / no true or false
Choice list One of the values you list; the assistant reads your label for it
ID Letters, digits, _ and -, up to 64 characters

A value that does not fit its type is dropped, never cut short: a shortened value can say something you did not mean. Variables declared before types existed are treated as Text, with the 200-character limit.

When Security → Require signature is on, Ragtime only accepts values it can prove came from your server. That means signing:

  • every chat page link that carries values (external variables or injection),
  • every form POST to the chat page, and
  • every direct API call from your backend (send the signature as an X-Ragtime-Signature header).

A plain link with no values (or only ?lang=) needs no signature. A signature is valid for 5 minutes, so generate the URL when your page is rendered. The signature is an HMAC-SHA256 over the sorted query string (including timestamp, excluding signature) followed by the raw request body, if any. You cannot use a static HTML snippet; generate the URL on your server:

import crypto from 'crypto';
/**
* Generate an HMAC-SHA256 signature for request verification.
* The signing secret is found in Dashboard → Assistant → Deployment → Security.
*/
function generateHmacSignature(secret: string, payload: string): string {
return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}
async function generateSignedUrl() {
const baseUrl = 'https://your-ragtime-instance.com/chat/org-slug/assistant-slug';
const params = new URLSearchParams();
// Add any query parameters
params.append('user_name', 'John');
// Generate signature
const timestamp = Date.now().toString();
params.append('timestamp', timestamp);
params.sort();
const payload = params.toString();
const signature = generateHmacSignature(process.env.RAGTIME_SIGNING_SECRET!, payload);
params.append('signature', signature);
return `${baseUrl}?${params.toString()}`;
}

The signing secret is found in Dashboard → Deployment → Embedding → Security. Keep it server-side; never expose it to the client.

A link that carries values but is not signed (or whose signature is wrong or older than 5 minutes) is answered with 401 Invalid signature instead of the chat. If signing is on but no secret is set, the page answers with an error rather than accepting unchecked values.

Only the sites listed under Dashboard → Deployment → Embedding → Allowed Domains may show the assistant in a frame (subdomains included). With none listed, it cannot be embedded anywhere. Opening the chat link directly always works.

A site that is not listed sees a short message in the frame naming itself and this setting. If the browser does not say which site is embedding, it blocks the frame on its own. An LMS must be listed too, including the domain it serves content from.

The embedded assistant communicates with your page via postMessage. Add this listener to capture lifecycle events and AI-triggered actions:

// Listen for Ragtime chat events and actions
window.addEventListener('message', (event) => {
const { data } = event;
// Lifecycle events:
// chat_started, thinking, response_start, response_given, emotion,
// session_ended_idle, session_ended_natural, session_restarted, listening,
// stage_changed,
// session_ended_user, session_ended_remote, session_ended_max_duration, session_ended_error,
// idle_timeout_start, idle_timeout_tick, idle_timeout_stop,
// mic_timeout_start, mic_timeout_tick, mic_timeout_stop, mic_max_duration
//
// Transcript events (opt-in via Deployment settings):
// transcript_user, transcript_assistant
if (data?.type === 'ragtime-event') {
console.log('[Ragtime Event]', data.event, data.detail);
}
// Page events sent by an action (see Actions → Steps → Page event)
if (data?.type === 'ragtime-action') {
console.log('[Ragtime Action]', data.action, data.args);
}
});
// Optional: let the host application drive a conversation stage.
// Requires conversation stages on the assistant.
const frame = document.querySelector('iframe[name="ragtime-chat"]');
frame?.contentWindow?.postMessage(
{ type: 'ragtime-host', action: 'set-stage', stageId: 'stage-3' },
'https://your-ragtime-instance.com'
);
Category Events
Chat lifecycle chat_started, thinking, response_start, response_given, emotion
Session end session_ended_idle, session_ended_natural, session_ended_user, session_ended_remote, session_ended_max_duration, session_ended_error
Session control session_restarted, listening
Conversation stages stage_changed
Idle timeout idle_timeout_start, idle_timeout_tick, idle_timeout_stop
Mic timeout mic_timeout_start, mic_timeout_tick, mic_timeout_stop, mic_max_duration
Transcript (opt-in) transcript_user, transcript_assistant
Page events host_event_accepted, host_event_dropped (see Page events)

Your page can tell a running conversation what the visitor is doing: the room they tapped on a map, the product they are looking at, the quiz they just failed. Declare the events under Dashboard → Actions → Page events, turn Accept page events on for the channel (Deployment → Channels), and send them with postMessage:

const frame = document.querySelector('iframe');
frame.contentWindow.postMessage(
{ type: 'ragtime-host', action: 'event', name: 'place_selected', args: { place: 'spa' } },
'https://your-ragtime-instance.com'
);

Each event has one of three effects:

Effect What it does
Keep as state The latest value is read on every turn, silently. Send args: null to clear it.
Shape next reply Read once by the next reply, then forgotten.
Speak now The assistant responds straight away. At most 6 per session, at least 20 seconds apart.

Only declared events with values that fit their declared types are accepted, and the assistant never reads what your page sent: it reads the sentence you wrote for the event, with the values filled in. Every event is answered with host_event_accepted, or with host_event_dropped carrying a reason and a plain-language message:

window.addEventListener('message', (e) => {
if (e.data?.type === 'ragtime-event' && e.data.event === 'host_event_dropped') {
console.warn(e.data.detail.name, e.data.detail.reason, e.data.detail.message);
}
});

The starter HTML on Deployment → Custom template includes ready-to-paste code for the events you declared, and the dashboard preview has a Page events button for trying them before you have a page. The Page Events guide covers declaring events, their types and limits.

For a quick start, use the Starter Page download button in Dashboard → Deployment → Embedding. It generates a ready-to-open HTML file with your assistant embedded and an event log — perfect for testing your integration locally.

Customers can ship a fully custom HTML “shell” around the chat (header, branding, per-channel JS, etc.) and have Ragtime serve it as a real page. Configure it in Dashboard → Deployment → Custom template and toggle individual channels to use the template under Channels. Once a template is published, the channel’s public URL switches from /chat/<org>/<project>/<device> to /embed/<org>/<project>/<device>.

Everything above on this page works identically — just swap the URL. Query parameters, POST body variables, HMAC signatures, and the postMessage event stream are all forwarded between the outer /embed/... page and the chat iframe it wraps, so an external host that iframes /embed/... sees the same protocol as one that iframes /chat/... directly.

Inside the template itself, customer JS gets two extra conveniences:

  • window.addEventListener('ragtime:event', e => …) — chat events re-dispatched as DOM CustomEvents.
  • window.ragtime.send(type, detail) — post a message into the chat iframe.

The {{RAGTIME_IFRAME}} token in the template is replaced with the chat iframe at render time. Two extra tokens are substituted per-request so a single template can branch on which channel is hosting it:

  • {{CHANNEL_SLUG}} — URL-safe channel identifier (e.g. kiosk-a). HTML-escaped; safe to use as text content or inside double-quoted attribute values.
  • {{CHANNEL_NAME}} — human-readable channel label, HTML-escaped under the same rules.

The legacy {{DEVICE_SLUG}} / {{DEVICE_NAME}} tokens are still accepted for templates saved before this rename.

On the preview route (/embed/<projectId>) both tokens resolve to preview / Preview since the preview isn’t bound to a specific channel.

External Variables (declared under Deployment → Embedding → External Variables) work the same way for /embed/... as they do for /chat/... — values arrive via query string or POST body and are injected into the system prompt only if the variable is declared in the dashboard.