Rovenue SDK
FunnelsWeb

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):

MessageWhen
custom_actionA custom action ran, with its actionId and params
close_funnelThe 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).

On this page