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/journeysRender 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.
- Check access.
POST /api/v1/journeys/{journeyId}/validatewith the key in anAuthorization: Bearerheader. 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. - Fetch the config.
GET /api/v1/journeys/{journeyId}returns the published config, itsrevisionId, itsversion, and the Journey's contact-linking mode. Pass aconfigprop to skip this call — step 1 still runs, so a local config never bypasses access control. - Render and capture. Step events queue in the browser and go out as
POST /api/v1/journeys/{journeyId}/capture, ten per batch by default. - Finish. The
completeevent flushes at once. The SDK also tries an exit-safe flush onvisibilitychangeandpagehide.
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
- Load the page in your app. The first screen of your Journey renders.
- Answer a screen and continue. Reload the page — you come back to the same screen with your answer still filled in.
- Finish the flow, then open the Journey in FounderHQ. The response is
listed, and it carries the contact you passed in
identity.
Props
| Prop | What it does |
|---|---|
apiKey | Your publishable key. Required. |
journeyId | The Journey to render. Required. |
config | Render a config you already have instead of fetching it. Access is still checked with FounderHQ. |
storageKey | Where answers and progress are saved in the browser. |
identity | externalId, email, phone for contact linking. |
initialAnswers | Seed answers at first render, for data only your app knows (live pricing plans, for example). |
initialOptions | Replace an option list at render time, keyed by the answer variable. |
onEvent | Called for every Journey event. |
onOpenURL | Handle link buttons yourself instead of letting the browser navigate. |
onDiscountCodeApply | Validate a discount code and return new pricing. Your app owns the rules. |
capture | false turns off sending events to FounderHQ. An object adds custom context. |
theme | CSS class(es) for the Journey root, overriding the theme set in FounderHQ. |
className | Extra class on the Journey root. |
presentation | "web" (default) or "native" for an edge-to-edge app WebView. |
loadingComponent | What to show while the config loads. |
errorComponent | What to show when the config cannot load. |
baseUrl | Local 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.