# Attribution (https://www.getfounderhq.com/docs/analytics/concepts/attribution)

Every payment gets two answers, not one:

- **Introduced by** — the earliest qualifying touch inside the window.
  This is who found the customer.
- **Closed by** — the latest qualifying touch before the money. This is
  who finished the job.

A blog post that brought someone in six weeks ago and an ad they
clicked yesterday both did work. One number would hide one of them.

## What counts as a touch

A touch is a human `$session_start`, `$pageview`, or `$screen` that
carries a real source: a non-direct channel, or any ad click ID.

- A visit with no campaign and no referrer is not a qualifying touch.
- Bot visits never count. See
  [Bot traffic policy](/analytics/concepts/bot-traffic-policy).
- If no qualifying touch exists in the window, the payment is
  **Direct**. FounderHQ never promotes a random visit to fill the gap,
  and Direct never erases a known earlier source.

## The window

The window is counted back from the day the money was earned, not the
day the webhook arrived. That keeps a renewal on the renewal's own
merits. The default is 90 days. You can also use 30, 60, or 180. Each
result records the model and the window that produced it, so two
reports never disagree silently.

## Where the source comes from

The SDK reads campaign parameters and ad click IDs from the URL on
every landing, then keeps both the first and the latest value on the
contact. There are 24 keys: the five `utm_*` parameters plus 19 click
IDs from Google, Meta, Microsoft, LinkedIn, TikTok, Reddit, and others.
The full list is in the
[event taxonomy](/analytics/protocol-reference/event-taxonomy). Mobile deep
links and the Play install referrer feed the same keys.

## Keep the chain through checkout

The gap most products lose is checkout. Attach the visitor's IDs to the
payment so the payment can find the person.

```ts
const metadata = founderhq.checkoutMetadata();
// pass metadata into your Stripe or Dodo checkout session
```

Renewals do not carry that metadata, so FounderHQ also remembers the
provider customer once matched. See
[Checkout metadata and payment links](/analytics/revenue/checkout-metadata-and-payment-links).

If a payment cannot be matched to anyone, it is parked rather than
guessed, with candidate matches for you to confirm.

## When facts arrive late

A touch that shows up after a decision was made does not quietly
rewrite history. The decision is recomputed on request, and the new
answer is added as a new version, so a number you screenshotted last
week still explains itself.

## Related

- [Revenue](/analytics/revenue)
- [Sessions](/analytics/concepts/sessions)
- [Accounts and groups](/analytics/concepts/accounts-and-groups)
