How it works
An embed moves through these states:
Hard limits, always enforced server-side:
- Sessions end after 3 minutes idle or 10 minutes total (a countdown shows in the footer).
- Concurrent embed sessions share your org’s device concurrency. Visitors
beyond your plan’s limit get the
at_capacitystate. - Up to 50 embeds per app.
- The embed URL is the only credential. It can start a device session on the pinned app and nothing else. Revoking the embed disables the URL immediately.
- Sessions bill to your org like any direct device session.
Create an embed
In the Revyl app, open your app’s build detail panel and choose Embed. Pin the embed to a specific build or let it track the latest upload, then copy the iframe snippet. Revoke it from the same panel at any time. Base snippet:URL parameter reference
All parameters are optional, combine freely, and are purely presentational — none of them changes what the embed is allowed to do. Invalid values are ignored (the default applies). Unknown parameters are ignored.
Why you should always pin
theme: without it, the embed follows what the
browser reports inside the iframe — and for cross-origin iframes, browsers
replace the visitor’s OS preference with the embedding page’s own
color-scheme CSS. If your site declares a scheme (most do), the embed
inherits that; if not, it follows the visitor’s OS and may flip dark on your
light page. theme=light or theme=dark removes the guesswork.
What is never configurable: the “Powered by Revyl” footer, the session
countdown, and the visitor’s Stop button always render. Anonymous visitors
must always be able to end a session they started.
Recipes
Every combination, ready to adapt. ReplaceYOUR_EMBED_ID and the origin as
needed.
1. Default video-style embed
2. Branded poster
Custom copy and button color on top of your uploaded poster image:3. Transparent blend-in
The embed shows only the device frame and footer; your page shows through everywhere else. All threestyle properties on the iframe are required —
see Transparent mode for why:
theme that keeps the gray footer text readable against your
page background: theme=light (near-black text) for light pages,
theme=dark (light-gray text) for dark pages.
4. Fully host-controlled (your own start button and states)
controls=0 hides every built-in screen; your page renders the UI and
drives the session over postMessage:
revyl-embed-ended, revyl-embed-at_capacity, and
revyl-embed-error to render your own overlays or retry buttons — see the
JavaScript API. When a session ends, the embed returns to
the (chromeless) poster; sending revyl-embed-start again restarts in place,
so your restart button can live anywhere on the page.
5. Invisible integration — just the device on your page
Combines 3 and 4: no built-in chrome, no background, only the device frame and the mandatory footer. This is the maximum blend-in configuration:6. Site with a light/dark toggle
Pin the initial theme in the URL, then forward your toggle through postMessage so the embed follows it live:Transparent mode
Withtransparent=1 the embed’s own background disappears and the footer
loses its top border. The device frame, poster, and footer text still render.
The iframe element on your page must cooperate — all three properties:
border:0— removes the browser’s default inset iframe border.background:transparent— any background you set on the iframe element paints behind the (now transparent) embed.color-scheme:light dark— required for Safari. Browsers paint an opaque canvas behind a cross-origin iframe when their color schemes might conflict. Chrome only blocks on an actual conflict, but Safari requires the embedding element to explicitly declare scheme support; without this property, transparency silently fails in Safari (you get an opaque white box). Declaringlight darkon both sides — the embed already declares it internally — makes the frames compatible in every engine.
Upload a poster image
The poster is shown as the device’s screen before the session starts. Upload one with your org API key:Launch variables
Embeds can apply your org’s launch variables — environment values or iOS launch arguments injected when the app starts, such as a demo-account auth token — so anonymous visitors land in exactly the app state you want. Attach variables when creating the embed (passlaunch_env_var_ids on the
create call, or pick them in the embed dialog). Every session the embed
starts then applies them. Two properties hold by design:
- Values never reach the visitor. Secret values are decrypted server-side, injected at app launch, and scrubbed from logs and streams. The public embed endpoints never return variable ids, keys, or values.
- The selection is fixed by your org. Nothing a visitor (or the embedding page) sends can add, remove, or change which variables apply.
Minted session URLs (per-session values)
When each session needs different values — say your users preview their own content inside your app — mint a one-time session URL from your backend instead of hard-coding one embed per case:https://app.revyl.ai + embed_path (all other URL
parameters combine with mint as usual). The typical flow:
- Your user clicks “Preview” in your product.
- Your backend authorizes them, then calls the mint API with that user’s values.
- Your frontend loads the returned
embed_pathin the iframe (or updates an existing iframe’ssrc).
- Single-use. The token burns when the session starts (or on any invalid redemption). A visitor restarting the demo gets a session with only the embed’s stored variables — mint a fresh URL per preview.
- TTL-bound.
ttl_seconds(60–3600, default 600) caps how long an unredeemed URL stays valid. Leaked links die on their own. - Values stay server-side. The token is opaque and carries no encoded data; the browser never sees your values.
- Merge order. Embed-attached variables apply first, minted variable
ids second, inline
env_varslast — later wins per key. - Validation at mint time. Inline keys follow launch-variable key rules
(no
DYLD_/SIMCTL_CHILD_/COGNISIM_prefixes), values are capped at 8 KB, max 20 inline values, and stored-variable ids must belong to your org.
JavaScript API
The embed exchangespostMessage events with the page that embeds it. No
SDK, no keys — the messages below are the entire API.
Commands (your page → the embed)
Send withiframe.contentWindow.postMessage(message, "*"). The embed only
accepts commands from its direct parent window — sibling frames, popups,
and nested frames are ignored.
Events (the embed → your page)
Listen onwindow and filter by source, since the embed posts with
targetOrigin: "*":
There is no event for the poster or starting states — drive your loading UI
from the moment you send
revyl-embed-start until revyl-embed-started
arrives.
Troubleshooting
Embeds can be framed by any site (no ancestor allowlist) — access control is
the unguessable embed URL plus per-session tokens, not framing restrictions.