Resumable Sessions
By default every conversation is a clean slate. Close the tab and it’s gone — nothing about the chat is stored against the visitor.
Resumable sessions change that for a device you choose. The conversation is kept on Ragtime’s side and handed back when the visitor returns, so they continue mid-thread instead of starting over. It works for anonymous visitors — there is no Ragtime login involved.
Before you start
Section titled “Before you start”Resumable sessions require Store conversations to be on for the project (Dashboard → Analytics → Conversation storage). Resuming means handing a stored conversation back to the visitor, so it can’t be switched on for a project that has opted out of keeping conversations — the device toggle stays unavailable and tells you why.
Turning conversation storage off later stops resume everywhere in that project immediately, including for conversations already saved.
Turn it on
Section titled “Turn it on”Dashboard → Deployment → Devices → edit a device → Resumable sessions.
| Setting | What it does |
|---|---|
| Resumable sessions | The master switch. On its own, this gives you same-browser resume (see below). |
| Keep sessions for (days of inactivity) | How long an untouched conversation survives before it’s deleted. 1–365, default 30. The clock restarts on every message. |
| Continue across devices | Option A — resume by your user ID. Requires request signing. |
| Let the host page manage the session | Option B — your page holds the session token. |
Level 1 — same browser (nothing to build)
Section titled “Level 1 — same browser (nothing to build)”Flip Resumable sessions on and you’re done. Ragtime keeps a private token in the visitor’s browser storage and reuses it when they come back to the same device on the same browser.
This covers the common case — someone reads half an answer, closes the tab, and returns that evening on the same laptop.
What it can’t do: follow the visitor to their phone, survive a cleared browser, or work in a private window. Those need one of the two options below.
A conversation that reached a natural ending is not resumed — the visitor gets a fresh start next time, which is usually what you want.
Level 2 — Option A: continue across devices
Section titled “Level 2 — Option A: continue across devices”Use this when your site already has logged-in users. You tell Ragtime who the visitor is, using your own user ID, and Ragtime hands back that person’s last conversation on whatever device they open next.
What you need
Section titled “What you need”- Request signing enabled: Dashboard → Deployment → Security → Require signature, which gives you a signing secret.
- A server-side place to build the URL. This will not work from browser JavaScript, by design — see the warning below.
What you send
Section titled “What you send”Add three query parameters to the iframe URL:
| Parameter | Value |
|---|---|
ragtimeUserId |
Your own stable ID for the logged-in visitor. |
timestamp |
Current time in milliseconds. Valid for 5 minutes. |
signature |
HMAC-SHA256 over the sorted parameters (below). |
import crypto from 'crypto';
function buildResumableChatUrl(baseUrl: string, secret: string, userId: string) { const params = new URLSearchParams(); params.set('ragtimeUserId', userId); params.set('timestamp', Date.now().toString());
// Sort, then sign the resulting query string. `timestamp` is just one of the // sorted parameters — it is not prefixed separately. params.sort(); const signature = crypto .createHmac('sha256', secret) .update(params.toString()) .digest('hex');
params.set('signature', signature); return `${baseUrl}?${params.toString()}`;}
// Render this server-side, per page load — the timestamp expires after 5 minutes.const src = buildResumableChatUrl( 'https://<host>/chat/<orgSlug>/<projectSlug>/<deviceSlug>', process.env.RAGTIME_SIGNING_SECRET!, currentUser.id);This is the same signing scheme used elsewhere in these docs — if you already
sign injection parameters, reuse that helper and add ragtimeUserId to it.
What Ragtime stores
Section titled “What Ragtime stores”Not your user ID. Ragtime stores a one-way fingerprint of it, so the ID itself is never at rest on our side. You keep the mapping; we only ever match a fingerprint we’re handed against one we’ve seen before.
What the visitor sees
Section titled “What the visitor sees”Their previous conversation, on any device, as long as your page identifies them. First visit for a given ID is an ordinary fresh chat.
Level 3 — Option B: your page holds the token
Section titled “Level 3 — Option B: your page holds the token”Use this when you want portability but don’t have logged-in users — or when you’d rather own where the conversation token lives (your own cookie, your own account record, your own app storage).
The widget hands you an opaque token; you keep it; you hand it back later.
The exchange
Section titled “The exchange”Three messages, over postMessage between the iframe and your page:
| Direction | Message | When |
|---|---|---|
| Widget → your page | { type: 'ragtime-session', status: 'ready' } |
On load. Your cue to reply if you have a stored token. |
| Widget → your page | { type: 'ragtime-session', status: 'active', sessionId, expiresAt } |
After each save. Store sessionId. |
| Your page → widget | { type: 'ragtime-host', action: 'resume', sessionId } |
Your reply to ready. |
<iframe id="ragtime" src="https://<host>/chat/<orgSlug>/<projectSlug>/<deviceSlug>" width="100%" height="600px" frameborder="0" allow="microphone; camera; display-capture; autoplay"></iframe>
<script> const RAGTIME_ORIGIN = 'https://<host>'; const frame = document.getElementById('ragtime');
window.addEventListener('message', async (event) => { if (event.origin !== RAGTIME_ORIGIN) return; if (event.data?.type !== 'ragtime-session') return;
if (event.data.status === 'ready') { // Hand back whatever you stored for this visitor, if anything. const stored = await yourBackend.getRagtimeSession(); if (stored) { frame.contentWindow.postMessage( { type: 'ragtime-host', action: 'resume', sessionId: stored }, RAGTIME_ORIGIN ); } }
if (event.data.status === 'active') { // Persist for next time. Server-side storage is strongly preferred. await yourBackend.saveRagtimeSession(event.data.sessionId, event.data.expiresAt); } });</script>Two rules
Section titled “Two rules”Answer ready quickly. The widget waits about a second, then gets on with
greeting the visitor. If your lookup is slow, do it before the iframe loads
and have the answer ready. Missing the window isn’t fatal — you’ll still receive
the token on the first save — but that visit won’t resume.
Never put the token in a URL. It is a read-access key to that conversation:
anyone holding it can read the transcript. URLs leak through referrer headers,
browser history, server logs and copy-paste. Keep it in your backend, or at
minimum an HttpOnly cookie.
Choosing between them
Section titled “Choosing between them”| Your situation | Use |
|---|---|
| Public site, no accounts, “good enough” continuity | Level 1 alone |
| Visitors log in to your site | Option A |
| No accounts, but you want continuity across devices or your own app | Option B |
| You have accounts and a native app | Both — Option A wins when both are available |
Options A and B layer on top of Level 1 rather than replacing it: if Option A has no signed user, or Option B’s handshake goes unanswered, the visitor’s own browser token is still tried before starting fresh.
What carries over — and what deliberately doesn’t
Section titled “What carries over — and what deliberately doesn’t”Carries over: the conversation itself, the selected language, the point the assistant had reached in a multi-step persona, and the visitor’s position in a guided form (including answers already given).
Deliberately does not: anything that did something. If a step in your guided form triggered a webhook, sent an email, or called your API the first time through, resuming does not fire it again. Ragtime restores where the visitor was standing, never replays what happened. A returning visitor mid-form picks up at the next unanswered question.
If you change a guided form’s design while someone has a conversation paused inside it, their saved position may no longer fit the new flow. That’s handled: they get a normal fresh greeting rather than a broken form.
Not supported: resuming mid-flow in live voice/avatar conversations. Those run on a different pipeline. Resumable sessions target text and text-with-avatar chat.
Privacy and retention
Section titled “Privacy and retention”- The stored conversation is a live record, kept as written — not anonymised, because it has to be shown back to the visitor verbatim. It is separate from the anonymised transcripts behind analytics and insights.
- It is deleted automatically once the retention window passes with no activity. Shorter windows mean less data at rest — 30 days suits most sites; pick lower if your conversations touch sensitive ground.
- Turning the device setting off — or conversation storage off project-wide — makes existing conversations immediately unresumable.
- Anyone holding the session token can read that conversation. That is what makes resume work without a login, and it is why Option B insists you keep the token out of URLs.
- If your privacy notice enumerates what you store about visitors, enabling this is a change worth reflecting there.
Deleting stored conversations
Section titled “Deleting stored conversations”Devices → edit the device → Stored conversations → Delete stored conversations. It shows how many are held and erases them immediately. Available to org admins and project owners, and recorded in your audit log.
Unresumable is not the same as deleted: switching the feature off stops conversations being served back, but they remain until they expire. Use the delete button when you want the data gone now.
Because sessions are anonymous, there is no way to look one up by name — so an erasure request for a specific visitor is served by deleting the device’s stored conversations. Shorter retention windows reduce how often this comes up.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause |
|---|---|
| The toggle is greyed out | Store conversations is off for the project — turn it on under Analytics → Conversation storage first. |
| Nothing ever resumes | Resumable sessions is off for that specific device, the device is disabled, or conversation storage was switched off project-wide. |
| Resumes on desktop, not on mobile | That’s Level 1 working as designed — browser storage doesn’t travel. You need Option A or B. |
| Option A always starts fresh | The signature isn’t validating: the secret differs, the parameters weren’t sorted before signing, or the timestamp is older than 5 minutes. An unsigned ragtimeUserId is ignored silently. |
Option B never receives ready |
Your message listener is checking the wrong origin, or it’s registered after the iframe has already loaded. |
| A very long conversation resumes part-way in | Past the platform’s message ceiling only the most recent turns are kept, so resume lands near the end rather than the very beginning. |
| A conversation you expected to resume starts fresh | It probably ended naturally — a farewelled conversation is not resumed by design. |