Documentation
Revenue

Checkout metadata and payment links

Attach FounderHQ attribution to checkout metadata and payment links.

A payment carries no attribution on its own. Your checkout has to carry two IDs from the visitor. This page explains the IDs, where to put them, and what to do when the checkout page is not yours.

The two IDs

fhq_anonymous_id   the visitor, across sessions
fhq_session_id     the visit that led to the purchase

Read them in the browser:

import { founderhq } from "@founderhq/events";

const identity = founderhq.checkoutMetadata();
// { fhq_anonymous_id, fhq_session_id }

The browser returns an empty object when the visitor has not granted consent. Treat that as normal and complete the sale without attribution.

If you set an account on the SDK, the same call also returns fhq_account_key and, when one is issued, fhq_account_context. Pass them through untouched.

Build the same object on your server with the Node SDK:

import { checkoutMetadata } from "@founderhq/events-node";

const attribution = checkoutMetadata({
  anonymousId: identity.fhq_anonymous_id,
  sessionId: identity.fhq_session_id,
});

It throws when either ID is empty, so wrap it if a missing ID must not block the sale.

Where the IDs go

How you chargeWhere the IDs go
Stripe Checkout SessionSession metadata, plus subscription_data.metadata for subscriptions or payment_intent_data.metadata for one-time payments
Stripe PaymentIntentThe PaymentIntent's metadata
Stripe Payment Link or Pricing TableA minted token in client_reference_id
DodoThe metadata object at checkout creation
Mobile storesNot metadata. A UUID in Apple's appAccountToken or Google's obfuscatedExternalAccountId

The renewal trap

Session metadata describes one checkout. It does not travel to next year's renewal.

Put the metadata on the object that renews as well: the Stripe subscription for subscriptions, the PaymentIntent for one-time payments. FounderHQ also binds the provider's customer and subscription IDs on the first payment, so a renewal with no metadata at all still resolves to the same contact.

FounderHQ works out a renewal's channel from the contact's touches at the time of the renewal, then freezes it. Nothing later rewrites a payment.

Hosted pages you cannot edit

A Stripe Payment Link runs on Stripe's domain, so there is no place to put metadata. Ask the SDK for a token instead:

const token = await founderhq.paymentLinkToken();
  • The token starts with fhqref_ and is valid for 30 days.
  • FounderHQ mints it server-side, so attribution does not depend on the buyer returning to your site.
  • It returns null when consent is denied, when the visitor opted out, or when the page origin is not allowed for your publishable key.

Put it in client_reference_id on a Payment Link, or in the client-reference-id attribute on a Pricing Table.

Verify

Start a checkout, then open the object in your provider's dashboard. It shows both keys, or the link URL carries client_reference_id. After payment, FounderHQ fills in Introduced by and Closed by.

AI agent or LLM? Read this page as markdown

On this page