Documentation

Embed on the web

Embed a FounderHQ Journey on the web.

Render a published Journey inside your React app. About 5 minutes.

No code? Use the public link, the embed, or the phone preview from the Share button in the editor instead.

Before you start

You need a published Journey in FounderHQ, its Journey ID, and a publishable key (fhq_pk_XXXX) from the Journey embed preset. Open Journeys in FounderHQ to publish one and to copy its ID. See Journeys for how keys and allowed origins work.

Install the package

npm install @founderhq/journeys

Render the Journey

Import the component and the stylesheet. The stylesheet also loads the bundled Inter and Lora fonts, so the Journey looks the same in every browser without a font request to a third party.

import { Journey } from "@founderhq/journeys";
import "@founderhq/journeys/styles.css";

export function Onboarding() {
  return (
    <Journey
      apiKey="fhq_pk_XXXX"
      journeyId="journey_123"
      storageKey="onboarding"
    />
  );
}

apiKey and journeyId are always required. With nothing else passed, the SDK fetches the published config from FounderHQ and renders it.

storageKey is where the SDK keeps answers and the current screen in the browser. Give each Journey its own key so a person who reloads the page lands back where they left off.

Next.js App Router? Add "use client" at the top of the file. The Journey is an interactive client component.

What the SDK does on render

Everything goes to https://app.getfounderhq.com. You do not configure the host.

  1. Check access. POST /api/v1/journeys/{journeyId}/validate with the key in an Authorization: Bearer header. FounderHQ checks the key, the request origin, the brand, the Journeys entitlement, and that the Journey is published with an active version. A failure renders the error state and nothing else is sent.
  2. Fetch the config. GET /api/v1/journeys/{journeyId} returns the published config, its revisionId, its version, and the Journey's contact-linking mode. Pass a config prop to skip this call — step 1 still runs, so a local config never bypasses access control.
  3. Render and capture. Step events queue in the browser and go out as POST /api/v1/journeys/{journeyId}/capture, ten per batch by default.
  4. Finish. The complete event flushes at once. The SDK also tries an exit-safe flush on visibilitychange and pagehide.

The queue survives a reload. It holds up to 100 events for 24 hours, keeps the session, revision, and context each event was created with, and retries a failed batch later. Every event carries an ID, so a duplicate delivery is counted once. A 429 is obeyed: the SDK waits out Retry-After before it tries again.

baseUrl exists for local testing only. The web SDK honors a localhost value and falls back to https://app.getfounderhq.com for anything else.

Tell FounderHQ who the person is

If your app already knows the person, pass identity. FounderHQ uses it to link the response to a contact.

<Journey
  apiKey="fhq_pk_XXXX"
  journeyId="journey_123"
  identity={{
    externalId: "usr_1042",
    email: "jane@acme.com",
    phone: "+1 212 555 0123",
  }}
/>

Any non-empty identity field wins over an email, phone, or ID the person typed into the Journey. Their typed answers are still saved in the response. Pass only the fields you actually know.

React when the Journey finishes

onEvent fires as the person moves through the flow. Use the complete event to send them onward.

<Journey
  apiKey="fhq_pk_XXXX"
  journeyId="journey_123"
  onEvent={(event) => {
    if (event.type === "complete") {
      window.location.href = "/dashboard";
    }
  }}
/>

Event types: session_start, step_view, step_submit, navigate, complete, and purchase_intent. Each event carries answers (what the person entered) and computedVariables (values derived from those answers), kept separate.

Verify

  1. Load the page in your app. The first screen of your Journey renders.
  2. Answer a screen and continue. Reload the page — you come back to the same screen with your answer still filled in.
  3. Finish the flow, then open the Journey in FounderHQ. The response is listed, and it carries the contact you passed in identity.

Props

PropWhat it does
apiKeyYour publishable key. Required.
journeyIdThe Journey to render. Required.
configRender a config you already have instead of fetching it. Access is still checked with FounderHQ.
storageKeyWhere answers and progress are saved in the browser.
identityexternalId, email, phone for contact linking.
initialAnswersSeed answers at first render, for data only your app knows (live pricing plans, for example).
initialOptionsReplace an option list at render time, keyed by the answer variable.
onEventCalled for every Journey event.
onOpenURLHandle link buttons yourself instead of letting the browser navigate.
onDiscountCodeApplyValidate a discount code and return new pricing. Your app owns the rules.
capturefalse turns off sending events to FounderHQ. An object adds custom context.
themeCSS class(es) for the Journey root, overriding the theme set in FounderHQ.
classNameExtra class on the Journey root.
presentation"web" (default) or "native" for an edge-to-edge app WebView.
loadingComponentWhat to show while the config loads.
errorComponentWhat to show when the config cannot load.
baseUrlLocal testing override. Only a localhost value is honored.

Injecting data your app owns

Some screens need values that live in your app, not in the Journey config — live prices from StoreKit or your own API, or a country list. Pass them in initialAnswers and reference them from the Journey config with a template.

<Journey
  apiKey="fhq_pk_XXXX"
  journeyId="journey_123"
  initialAnswers={{
    pricingPlans: [
      {
        id: "pro_monthly",
        name: "Pro",
        price: {
          amount: 29,
          currency: "USD",
          period: "month",
          trial: { days: 7 },
        },
      },
    ],
  }}
/>

Keys you declared as computed variables are ignored here. Their formulas always win.

Good practice

  • One key per brand. A Journey embed key only reads Journeys in the brand it was created for. Do not carry one key to a second brand.
  • List every origin you serve from. Staging and preview domains are separate origins. A missing one fails with 403.
  • Test against localhost. Run FounderHQ locally and pass baseUrl="http://localhost:3000". Any other override is ignored.
  • Give each Journey its own storageKey. Two Journeys sharing one key restore each other's answers.
  • Leave capture on. Turning it off with capture={false} also removes responses and funnel analytics for that embed.

See Limits and reliability for the batch, body, and retry rules the SDK already follows for you.

Troubleshoot

The Journey shows "This Journey is unavailable"

The access check or the config fetch failed. Check that the Journey is published, that the Journey ID matches, that the publishable key belongs to the same brand as the Journey, and that the page's origin is on the key's allowed-origin list.

The Journey renders with the wrong fonts

Import @founderhq/journeys/styles.css. Without it the Journey inherits your page's fonts and loses its own layout rules.

Answers come back with no contact attached

Pass identity with at least one of externalId, email, or phone. Without it, FounderHQ can only link a contact when the person types an email or phone into the Journey itself.

AI agent or LLM? Read this page as markdown

On this page