When to use this endpoint
Tokens are short-lived (default 10 min, max 30 min) and origin-locked. Mint one server-side per session so your
wai_ key never reaches the browser.
One-call DX
The response gives you the token, the fully formed iframe URL, and a copy-pasteable<iframe> snippet. No client-side URL assembly.
iframe_html into your page server-rendered and you’re done.
Prerequisites
Before this endpoint works for a scenario:- Embedding must be enabled on the scenario (
embed_enabled: true). Toggle it in the Studio Share modal or via the Scenarios API. - Allowed origins must be set on the scenario (
embed_allowed_origins) so the iframe is only renderable from your domains. The token won’t validate against any origin that isn’t on this list.
Request body
string
required
UUID of the scenario to embed. Must be owned by the caller’s membership and have
embed_enabled: true.string
End-user identity. The token’s identity key. Resolves to a per-creator participant record so the same end user across multiple meetings is recognised as one identity (enables cross-session participant memory, accurate analytics, and a coherent dashboard view). Also resolves the
{{USER_EMAIL}} personalization token in the meeting script. Omit for a fully anonymous embed — the session still runs, but there’s no persistent identity and no cross-meeting memory.string
UUID of a participant you pre-created via
POST /v1/participants (advanced). Use when you want to control the participant record explicitly — for example, building a roster ahead of time. Wins over email if both are sent. Must belong to the scenario’s organisation.string
Metadata only — not used for identity resolution. Your local user ID on the partner side. Stored on the participant record as
external_ref so you can reconcile our participant rows against your own user table. Send it alongside email; on its own (no email) it has no identity effect and the session runs anonymously. Max 128 chars.string
Display name shown in the meeting UI (e.g., on the join screen). Also resolves the
{{USER_FIRST_NAME}} and {{USER_NAME}} personalization tokens in the meeting script. Max 80 chars.string[]
Per-token origin allowlist. Must be a subset of the scenario’s
embed_allowed_origins. Use this when one scenario is embedded across multiple sites and you want a token scoped to one of them. Each entry must be a bare HTTPS origin like https://partner.example.integer
Token lifetime. Range
30–1800. Default 600 (10 min).string
Which iframe route the snippet points at. One of:
button(default) — floating start button that opens the meeting in an overlayinline— full meeting UI inline in the pageavatar-assist— floating avatar widget in the cornersidebar— docked chat panel that becomes a call. Ships as a loader<script>rather than an iframe, so it returnssnippetand a nulliframe_html(see below).
string | number
Iframe width on
iframe_html. CSS string ("100%", "640px", "50vw") or a positive px number. Default "100%".string | number
Iframe height on
iframe_html. Same shape as width. Default 640.Response
string
Signed JWT. Treat as a credential — short-lived but powerful inside its TTL.
integer
Unix seconds at which the token stops validating.
string
Fully formed embed URL with
?t=<token> baked in. Use this if you want to construct your own iframe.string | null
Copy-paste-ready
<iframe> snippet with the URL, sizing, and the WebRTC allow attributes already set. null for sidebar, which mounts itself and owns its own iframe.string
The copy-paste embed code for whichever variant you asked for — the same iframe HTML for iframe variants, the loader
<script> for sidebar. Prefer this over iframe_html; it is always the right thing to paste.Variants
Sidebar chat
A chat panel docked to the edge of every page, with the persona’s face at
the top. The visitor types; when they click the avatar the same session
becomes a live call — no reconnect, and the assistant keeps the whole
conversation. Best as a website-native assistant on marketing and docs
sites.
Button
Floating start button that opens the meeting in a modal overlay. Default. Best when you’re adding embed to an existing page without layout changes.
Inline
Full meeting UI rendered inline. Best for training portals, LMS, or dedicated meeting pages where the embed is the main content.
Avatar Assist
Compact avatar widget in the corner. Best for support bots and onboarding nudges.
Installing the sidebar
The sidebar is the one variant you don’t paste as an iframe. A live WebRTC session can’t be moved between iframes without being torn down, so the chat panel and the popped-out call have to be the same document — which means the loader script owns the iframe and its geometry:
Including the script twice is a no-op, so it’s safe to drop into a tag
manager. To drive it from your own UI, use
window.WaterrWidget.open(),
.close(), .toggle() or .destroy().
Single-page apps
If the assistant belongs on some routes and not others, insert the tag when you enter those routes and calldestroy() on the way out — it removes the
launcher, closes the panel and gives the page back any width the panel was
borrowing. Inserting the tag again afterwards mounts a fresh widget, so the
usual mount/unmount pair works:
destroy(), so gate on
whether the assistant belongs on the page rather than on the page itself —
otherwise every click hangs up on a visitor mid-sentence. And handlers
registered with Waterr.on go with it; register them again after
re-inserting the tag (the window.Waterr = { q: [] } stub covers the case
where the script hasn’t landed yet). Route changes themselves need nothing:
the loader wraps history.pushState, so page views are reported for you.
Nothing connects when the panel opens. The session — and the billing that
comes with it — starts on the visitor’s first message, or when they open the
call. An idle chat hangs itself up after a few quiet minutes.
Token refresh
Tokens expire (default 10 min). If the iframe is loaded but the user hasn’t started the session yet when the token expires, the session start will be rejected. Two ways to handle this in practice:- Mint on demand. Generate the token at the moment the page renders, not ahead of time. Server-render the
iframe_html. Don’t cache the response. - Mint a fresh token client-side just before opening the modal. If you’re using
variant: "button"and the user might sit on the page for a while, refresh the token via an AJAX hit to your own backend (which proxies to this endpoint) right before opening the iframe.
ttl_seconds (max 1800 = 30 min). Going much higher is intentionally not supported — leaked tokens should go stale fast.
Origin lock
The token’s claims include the origin allowlist, and the iframe enforces it at session start. A leaked token presented from an origin not on the list is rejected.allowed_origins, the token inherits the scenario’s full embed_allowed_origins list.

