Skip to main content
Instead of sending traffic to a Play-hosted URL, you can embed the Funnel Page sign-up drawer directly into your own marketing site. A loader script mounts an overlay that opens on demand and carries the whole sign-up flow — email capture and one-time passcode (OTP) verification — without sending the visitor anywhere else.

Prerequisite: a first-party subdomain

You delegate a subdomain to Immutable by CNAME (for example landing.studio.com), and Immutable serves the drawer from it. You then add a <script> tag to your own marketing page (for example www.studio.com), which mounts an iframe pointing at your delegated subdomain.
The delegated subdomain and the page framing it must share a registrable domain — the same apex, per the public suffix list. landing.studio.com and www.studio.com both resolve to studio.com, so they qualify; under a two-part public suffix the apex is one level deeper (studio.co.uk, not co.uk). Matching apexes are what make the browser treat the iframe as first-party, so the session started inside it survives.When they don’t match, sign-up runs fine through email entry and the OTP step, then fails silently right after. Watch for preview hosts: vercel.app is itself a public suffix, so my-app.vercel.app is its own apex and fails the same way. Always test from a genuine subdomain of the apex you delegated.

Install

Replace landing.studio.com with your own delegated subdomain.
  • Place the tag right before </body> so <body> already exists and the drawer can mount immediately. In <head> it also works — it waits for DOMContentLoaded — but mounts slightly later.
  • Use a real <script src="..."> reference, not an inline script. The loader reads its own origin from the tag, so without src it does nothing.
  • Paste the tag once per page. A duplicate is detected and ignored.

Content Security Policy

This is the most common cause of a broken integration. landing.studio.com is a different origin from www.studio.com — the browser treats subdomains as distinct origins even on a shared apex, so script-src 'self' and frame-src 'self' do not cover your delegated subdomain. Until both are updated, the script and iframe loads are silently blocked. Add your delegated subdomain to both directives, merging into your existing policy rather than replacing it:
Immutable sets frame-ancestors on its side, scoped to your delegated subdomain’s apex and any subdomain of it, so any page under your own domain can frame the drawer with no extra configuration.

Zero-JavaScript path

The minimum integration is the script tag plus one attribute — no JavaScript of your own:
or, with a fallback destination:
Give every data-imtbl-cta element a real href. If the connection to the drawer fails, the loader stops intercepting clicks and the element degrades to a plain link instead of doing nothing — see Errors. The loader listens for clicks across the whole document, so elements added later by your CMS or client-side rendering work with no re-initialization. It does not intercept a click when:
  • The click was already handled. If your own handler on the element or an ancestor called preventDefault(), the loader leaves it alone.
  • It’s a modified or non-primary click. Cmd/Ctrl/Shift/Alt-click and any non-left-button click go to the browser, so <a data-imtbl-cta href="/signup"> still opens in a new tab on Cmd/Ctrl-click.

JavaScript API

Asks the drawer to open. This is intent only — the open event fires once the drawer confirms it has rendered, not synchronously when you call this.
Closes the drawer immediately: it goes invisible and click-through right away, without waiting on a reply from the frame.
Registers a handler for one of the events below, and returns a function to unregister it. ready is sticky — if it already fired, your callback runs immediately and synchronously.
Bootstrap queue for code that runs before the script loads. The script loads asynchronously, so your own inline setup above it runs first. Queue calls against window.imtbl as an array and the loader drains them on init:
Each queued entry is [methodName, ...args], using the same methods as above. After the loader runs, window.imtbl.push([...]) keeps working as an alias for calling the method directly.
You don’t need to queue anything just to load the drawer — the loader mounts its iframe as soon as it runs, so attribution is captured even for a visitor who never opens it.

Events

Register handlers with imtbl.embed.on(event, callback).
signup:completed carries no email, because any third-party script on your page could observe the event. The verified address reaches you through an authenticated export instead.

Errors

Either error is terminal for the rest of the page load: the drawer never becomes usable, and the loader stops intercepting clicks on data-imtbl-cta elements. An <a> reverts to a plain link and navigates to its href; a <button> with no href does nothing.

Attribution and query parameters

The loader reads your page’s query string and forwards it into the drawer’s URL, so ad-network click IDs and UTM parameters survive into attribution. Everything forwards except:
  • Reserved keys. parentOrigin, ref, href, and lang are populated by the loader from your page’s context, so a same-named query parameter is ignored.
  • iframe, always. Its presence tells Immutable’s own scripts to skip initialization, so forwarding it would silently disable attribution.
  • Keys that look like personal data, with a warning in debug mode — whole-word matches for terms including email, mail, phone/telephone/tel, fname/firstname, lname/lastname/surname, addr/address, passw/password, secret, ssn, and dob/birth/birthdate. Matching is on separator and camelCase boundaries (userEmail, user_email, firstname), not raw substrings (hotel_id and adobe_mc are not caught).
  • Any value containing @, regardless of key name.
  • Any single value over 512 characters, plus anything beyond a 2048-character total budget across everything forwarded — once spent, remaining parameters are dropped even if each would individually pass.
Only origin and path are forwarded as href — your page’s query string and hash are stripped, so a value already dropped by the per-parameter scan can’t slip back in.
Empty values are dropped as carrying no signal, except onboarding and linkStatus — presence alone is the signal there, so both ?onboarding and ?onboarding= forward (and a bare ?onboarding link opens the drawer automatically).

Debugging

Add data-mode="debug" to the script tag for console warnings prefixed [imtbl-embed] — dropped attribution parameters, handshake failures, and other loader-side problems:
Remove the attribute once you’re done; the console stays silent by default.

Versioning and caching

The script is served at a permanent path, /v1/embed.js — a breaking change ships at /v2/embed.js rather than changing what /v1/ returns underneath an integration you’ve already shipped. Within /v1/, the contents are intentionally mutable: fixes are patched in place so every studio’s integration gets them without editing their CMS. The tag therefore carries no Subresource Integrity (SRI) hash. Responses are served with Cache-Control: public, max-age=300, stale-while-revalidate=86400.

Next steps

Conversion Goals

Define which player actions count as a conversion

Engage

Nurture the signups you capture