supapulsedocs
Revenue packageAttribution

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

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.
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

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.

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.

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().

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

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

BreakdownRevenue is credited to
SourcesThe first or last touch source of the payment. Use the toggle to switch.
Pages, entry pages, exit pagesPageviews of the matched session
ReferrersThe referrer of the matched session
UTM campaignsThe first pageview with a utm_campaign in the matched session
Countries, devices, browsers, OSThe 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

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.

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

  • 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

  • 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.