# Attribution

URL: https://supapulse.app/docs/revenue/attribution

How Supapulse links a payment to the visit that first brought the customer, how renewals are handled, and what it doesn't cover.

## How revenue is linked to a visit [#how-revenue-is-linked-to-a-visit]

Every revenue event is credited to a first touch and a last touch. A touch is a pageview that carries a `utm_source` or a referrer. The first touch is the earliest such pageview, the last touch is the latest. When Supapulse finds none, both read `direct`.

To find the visit, Supapulse tries these in order and uses the first that matches:

1. **Visitor fingerprint.** Sent on the event, or stored when the customer was identified.
2. **Session ID.** Sent on the event, matched to pageviews with the same `session_id`.
3. **The customer's stored touch.** Saved the first time this customer was attributed. This is what renewals use.
4. **None.** The payment counts in totals and shows as `direct`.

```text
Visit from google.com ──► pageviews /pricing (referrer google.com)
        │
Signup ─┴─► supapulse.identify("user_123")  ──► customer user_123 gets first touch google.com
                                                         │
Stripe invoice.paid (renewal, months later) ─────────────┘  credited to google.com
```

## Linking a payment to a visit [#linking-a-payment-to-a-visit]

### identify [#identify]

Call `identify()` in the browser once the user is known. It sends your user ID and the current session ID with the public token. Supapulse builds the visitor fingerprint on the server from the request's IP address and user agent, the same way it does for pageviews, and looks up that visitor's pageviews.

```ts
supapulse.identify(user.id);
```

Use the same value as `customer_id` on revenue events. With Stripe, the customer key is the Stripe customer ID (`cus_…`); pass your own user ID in Stripe metadata as `supapulse_customer_id` and Supapulse links the two. See [Stripe](https://supapulse.app/docs/guides/stripe#link-stripe-customers-to-your-users).

### Checkout metadata [#checkout-metadata]

If a visitor can pay before you identify them, read the fingerprint and session ID when checkout starts and put them in the payment's metadata. This is the strongest link because it doesn't depend on a later `identify()`.

```ts
const visitor_fingerprint = await supapulse.getVisitorFingerprint(); // string | null
const session_id = supapulse.getSessionId();
```

For Stripe, set these metadata keys: `supapulse_visitor_fingerprint`, `supapulse_session_id` and `supapulse_customer_id`. For other processors, pass the same values as `visitor_fingerprint` and `session_id` on the revenue event.

## Renewals [#renewals]

The first payment from a customer saves its touch on the customer record. Every later payment from the same `customer_id` that can't be matched to a visit directly reuses that stored touch, so a renewal in month six is credited to the source that won the customer.

## The breakdowns [#the-breakdowns]

| Breakdown | Revenue is credited to |
| --- | --- |
| Sources | The first or last touch source of the payment. Use the toggle to switch. |
| Pages, entry pages, exit pages | Pageviews of the matched session |
| Referrers | The referrer of the matched session |
| UTM campaigns | The first pageview with a `utm_campaign` in the matched session |
| Countries, devices, browsers, OS | The matched session's visitor details |

Every breakdown except Sources joins revenue to the pageviews of the matched session, inside the selected date range. A payment with no matched session still counts in totals and in Sources.

## Refunds and MRR [#refunds-and-mrr]

A refund is stored as a negative amount, so revenue in the dashboard is net of refunds and the Refunds tile shows what was refunded. MRR is the running sum of `mrr_impact` across subscription starts, upgrades, downgrades and churn. Stripe events set it for you. For other processors, send `mrr_impact` yourself. See [API reference](https://supapulse.app/docs/revenue/api-reference#event-types).

Events with a `subscription_id` are reconciled per subscription. A `renewal` counts its `mrr_impact` only when Supapulse has no MRR recorded for that subscription yet, so subscriptions that started before you connected get a baseline from their first renewal. A `churn` removes exactly the MRR recorded for the subscription and ignores the value sent, so discounts don't leave a remainder and churn of a subscription Supapulse never saw doesn't go below zero.

## Limits [#limits]

- **Lookback.** Supapulse looks at pageviews from the last few days, following the site's salt rotation window: 1 to 30 days. A visit older than that isn't found by fingerprint. Identify the user while the visit is recent.
- **Cross-device journeys.** A phone visit and a laptop purchase are different visitors. `identify()` on the laptop can only link what the laptop did.
- **Session IDs.** A session ID covers one page load. On a multi-page site each full page load has a new one.
- **Currencies.** Reports add amounts without converting them.

## Getting good attribution [#getting-good-attribution]

- Call `identify()` right after login or signup, on the same device as the first visit.
- Pass `supapulse_customer_id` in Stripe metadata so Stripe's customer ID and your user ID are linked.
- Keep UTM parameters on your landing URLs; they're read from the pageview URL.
- Use the checkout metadata path when payment can happen before an account exists.
