Hosting & Embedding
Where a published web funnel runs — its slug, your own domain, and what a page that embeds it can hear.
A web funnel is published to a URL. A native funnel has none — it is served to a placement inside the app instead.
The funnel's own URL
A published web funnel is served at its slug by the public runner route,
which answers without a dashboard session. A web funnel's slug is unique
only within its project (funnels_project_slug_unique), so the URL
names both:
https://<your-dashboard-domain>/f/<projectId>/<slug>(apps/dashboard/src/routes/f.$projectId.$slug.tsx). The public loader
(GET /public/projects/:projectId/funnels/:slug) resolves the funnel by
project and slug and answers only for target = 'web'
(apps/api/src/routes/public/funnels.ts — loadPublishedConfigFromDb,
findBySlug): a converted-away row keeps its history, but no web URL
reaches it anymore. Only a funnel whose status is published with a
current version is served; anything else resolves to nothing.
Your own domain
Attach a custom domain to the project under Settings → Domains: add
the hostname, add the DNS records the page shows — a CNAME to the
deployment's edge host (CUSTOM_DOMAIN_EDGE_HOST) and a TXT record at
_rovenue.<hostname> with the verification token — then click Verify.
Verification checks the hostname and stamps it verified, while the
certificate stays pending until the issuer produces it
(apps/api/src/routes/dashboard/custom-domains.ts). A host resolves to a
funnel only once it is verified and its certificate is issued — an
unknown, unverified, or not-yet-issued host answers 404.
A verified domain serves every published web funnel of the project, one path per funnel:
https://<domain>/<slug>You can also pick one funnel as the domain's root funnel (same
Settings → Domains page); https://<domain>/ then serves it. Clearing the
root funnel, or archiving/deleting it, makes / 404 again — every other
/<slug> path is unaffected. On cloud, a project may verify at most one
custom domain; self-hosted has no limit. Because an apex domain (e.g.
acme.com) cannot be CNAMEd, point a subdomain at the edge host instead
(e.g. app.acme.com or get.acme.com). If you manage DNS through
Cloudflare, set the CNAME and TXT records to DNS only (grey cloud) —
proxying them breaks certificate issuance and hostname verification.
The dashboard resolves GET /public/host/lookup?host=<h>&path=<p> to find
which funnel a request on a custom domain names: { projectId, slug } for
a verified, cert-issued host, matching only the first path segment (the
rest belongs to the funnel, so /quiz/step-2 and /quiz?x=1 both still
name quiz).
The "Powered by Rovenue" badge
On Rovenue Cloud, a hosted web funnel of a project with no card on file
carries a small "Powered by Rovenue" link at the bottom of every screen. It
sits in its own row under the page, so it never covers the CTA, a sticky
footer or the checkout, and it is plain text in the funnel's own colours —
no image, no request to anywhere (packages/funnel-renderer/src/powered-by.tsx).
The API decides it, never the browser: the published-funnel payload carries
branding.poweredBy, which is true only on Rovenue Cloud, for a web
funnel, when the project's billing subscription is not active or
past_due (apps/api/src/services/funnel/branding.ts). Your own domain and
the /f/<projectId>/<slug> address show the same thing, and the builder's
Play through preview shows it exactly as visitors will.
To remove it, add a card under Settings → Billing: the next time the funnel loads, the badge is gone. The value is read on every request, not from the published-funnel cache, so there is no delay to wait out.
Native funnels never carry it — an app-store paywall is your app's own UI — and a self-hosted install never shows it.
Embedding
A web funnel can run in an iframe inside your own page. Two things can
reach the host page, and nothing else — custom actions and the close event
travel out through window.postMessage
(apps/dashboard/src/runner/host-bridge.ts:5-10):
| Message | When |
|---|---|
custom_action | A custom action ran, with its actionId and params |
close_funnel | The funnel ended |
(apps/dashboard/src/runner/host-bridge.ts:20-28).
function onCustomAction(actionId: string | undefined, params: Record<string, string> | undefined): void {
void actionId;
void params;
}
function onFunnelClosed(): void {}
window.addEventListener("message", (event) => {
const msg = event.data as {
source?: string;
type?: string;
actionId?: string;
params?: Record<string, string>;
} | null;
if (msg?.source !== "rovenue-funnel") return;
if (msg.type === "custom_action") onCustomAction(msg.actionId, msg.params);
if (msg.type === "close_funnel") onFunnelClosed();
});Custom actions are also pushed to window.dataLayer as
rovenue_funnel_custom_action, so a tag manager can pick them up without
any code (apps/dashboard/src/runner/host-bridge.ts:14,
apps/dashboard/src/runner/host-bridge.ts:40-45).
The envelope deliberately carries no session id and no visitor id. Messages
are posted to any origin, so anything in them would be readable by whoever
embeds the page — identifiers are capabilities, and none are sent
(apps/dashboard/src/runner/host-bridge.ts:6-10).
Talking to a running funnel
You cannot. A funnel reads its inputs once, from the URL that opened it:
utm_*, the referrer, and the declared app values seeded from the query
string (apps/dashboard/src/runner/session-inputs.ts:30-39,
apps/dashboard/src/runner/session-inputs.ts:40-53). Set them on the
iframe's src and they are there before the first screen renders — a
marketing link, an embed, and a native WebView all write the same query
string (apps/dashboard/src/runner/session-inputs.ts:7-12).
This is also how the funnel runs inside a native WebView: the app builds
the same URL, with the same query string. There is no separate mobile
embed (apps/dashboard/src/runner/session-inputs.ts:7-12).
Payments and Apple Pay
A web funnel's checkout takes payment on the page through your connected
Stripe account — see Checkout. Apple Pay
appears in that sheet only when the host the page was served from is
registered as a payment-method domain on the account taking the charge, so
registration runs per connected account
(apps/api/src/services/stripe/apple-pay-domain.ts:12-17).
The domain check is proved by an association file: one platform-wide copy
served from the funnel host verifies that host for every connected
account (apps/api/src/services/stripe/apple-pay-domain.ts:42-48). The
host registered is the one the paywall is served from — registering any
other host succeeds at the API level and Apple Pay silently never appears
(apps/api/src/services/stripe/apple-pay-domain.ts:99-110).
Publishing
What a funnel is checked for before it goes live, which findings block a publish and which only warn, and what visitors see while you keep editing.
Checkout
How a web funnel sells — the checkout action on its own screen, the Stripe gate on publish, and what the runner and the server do when the visitor pays.