# How to track Stripe revenue by source and campaign

> Connect Stripe Checkout, subscriptions, invoices, refunds, and webhooks to source, campaign, landing page, and session reports.

Canonical page: [https://attribiq.com/guides/stripe-revenue-attribution](https://attribiq.com/guides/stripe-revenue-attribution)

Category: Stripe & Payments

Last updated: 2026-10-02

Topics: stripe, revenue attribution, subscriptions, metadata

## Summary

Stripe knows the payment, customer, subscription, invoice, and refund. To attribute that revenue, your app must also pass the visitor and session context that existed before checkout. The safest pattern is to copy AttribIQ identifiers into Checkout Session metadata and subscription metadata, then process Stripe webhooks idempotently.

## What this guide covers

- Understand which Stripe objects matter for attribution
- Know why metadata is copied onto checkout and subscription objects
- Map Stripe webhook events into normalized revenue events
- Avoid common attribution mistakes around renewals and refunds

Stripe revenue attribution connects Stripe's payment lifecycle to the visits and campaigns that happened before the customer paid.

Stripe is the source for the money event. AttribIQ is the source for the browser and campaign context. The integration works when your app carries AttribIQ identifiers into Stripe metadata before checkout starts.

Start with a working [web analytics installation](https://attribiq.com/guides/install-attribiq). Confirm that a visit appears in Realtime and that its [traffic source](https://attribiq.com/guides/traffic-sources-referrers) and [landing page](https://attribiq.com/guides/content-and-landing-pages) are correct. These reports are useful before you connect payments; they also give you the acquisition context to validate against later.

If you are evaluating the product, see [Stripe revenue attribution software](https://attribiq.com/stripe-revenue-attribution). If you need purchases inside Google Analytics, use the separate [Stripe and GA4 integration tutorial](https://attribiq.com/guides/stripe-google-analytics-integration).

Stripe documents Checkout Sessions, metadata, and subscription webhooks in its official docs: [Checkout Sessions](https://docs.stripe.com/api/checkout/sessions), [metadata](https://docs.stripe.com/metadata), and [subscription webhooks](https://docs.stripe.com/billing/subscriptions/webhooks).

## The objects that matter

For a subscription SaaS product, these Stripe objects usually matter:

- Checkout Session for the initial checkout attempt.
- Customer for the paying account in Stripe.
- Subscription for recurring billing state.
- Invoice for renewals and failed payments.
- Charge or PaymentIntent for payment settlement and refunds.

The key mistake is only thinking about the first payment. If you want revenue by campaign, you also need to decide how renewals, upgrades, downgrades, refunds, cancellations, and churn should inherit or change attribution.

## The metadata bridge

The browser tracker knows the AttribIQ project, visitor, and session. Before checkout, your frontend sends that attribution object to your backend.

```js
const attribution = window.attribiq?.getAttribution?.() ?? null

await fetch("/api/create-checkout-session", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ price_id: "price_123", attribution })
})
```

The backend copies that context into Stripe. This example shows the attribution fields inside an existing authenticated Checkout handler. Resolve the allowed price and the project ID on your server; validate the visitor and session IDs before using them. The browser must not decide which account or project receives revenue.

```js
const metadata = attribution?.visitor_id && attribution?.session_id
  ? {
      attribiq_project_id: projectId,
      attribiq_visitor_id: attribution.visitor_id,
      attribiq_session_id: attribution.session_id
    }
  : {}

const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [{ price: priceId, quantity: 1 }],
  success_url: "https://example.com/billing/success?session_id={CHECKOUT_SESSION_ID}",
  cancel_url: "https://example.com/pricing",
  client_reference_id: attribution?.visitor_id || undefined,
  metadata,
  subscription_data: { metadata }
})
```

Copying metadata to both places matters. The Checkout Session helps with the first conversion. Subscription metadata helps with future invoice and subscription events.

Attribution is optional context, so a blocked tracker or unavailable browser identifier must not stop a customer from paying. Omit missing fields and keep that payment unattributed. Do not manufacture visitor IDs, put email addresses into metadata as analytics identifiers, or assume that identifiers survive a new device or every later visit. Respect the collection and consent settings used on your site.

## Choose the ingestion path

There are two Stripe ingestion paths.

For most teams, Stripe should send selected webhook events directly to AttribIQ's native receiver. AttribIQ verifies the Stripe signature when the endpoint secret is configured, ignores duplicate Stripe event IDs, extracts the AttribIQ metadata, writes the revenue event, and runs attribution.

Use a customer-owned relay only when your backend needs to normalize custom billing semantics before AttribIQ receives the event. In that path, your webhook handler should:

1. Verify the Stripe webhook signature.
2. Store the Stripe event ID and reject duplicate deliveries.
3. Extract AttribIQ metadata from the event object.
4. Normalize the event into purchase, subscription_started, renewal, upgrade, downgrade, refund, cancellation, or churn.
5. Use Stripe object IDs for idempotency.
6. Send the normalized revenue event to `POST /v1/revenue`.

Do not rely on redirect success pages as the source of truth. They are useful for UI, but webhooks are the reliable payment lifecycle feed.

## Attribution rules for renewals

Most SaaS teams attribute renewals back to the original acquisition source unless they have a reason to model expansion separately. That makes reports like "MRR by campaign" stable and understandable.

Refunds should subtract from the attributed source that received the original credit. Churn should be visible by source too. A campaign that creates many initial purchases but high refunds may be worse than a quieter channel with stronger retention.

## Worked example: campaign to renewal and refund

The following is an illustrative EUR subscription journey, not a customer result. Use one attribution model and one reporting currency when comparing the rows.

| Step | Evidence to retain | Expected reporting outcome |
| --- | --- | --- |
| A visitor lands on `/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=autumn` | Source, campaign, landing page, visitor and session IDs | A newsletter visit in web analytics, with no revenue yet |
| The visitor signs up and starts Checkout | Signup event plus the same IDs on Checkout and subscription metadata | A goal conversion and a checkout handoff |
| Stripe confirms the first €49 invoice | Paid invoice ID, amount, currency and attribution metadata | One €49 revenue event assigned to the known journey |
| Stripe delivers that webhook again | The same Stripe event ID | No second revenue event |
| A later €49 renewal is paid | A new invoice ID and the subscription's acquisition identifiers | Another €49, using the configured attribution model |
| €10 of that renewal is refunded | Refund ID and a link to the original payment | A €10 reduction against the credited journey |

The payment amounts in this example total €88 after the refund. That is a payment reporting example, not an MRR calculation or accounting definition of net revenue. A failed renewal adds no paid revenue. An unmatched payment still belongs in payment totals, with its missing attribution visible.

Read [UTM revenue attribution](https://attribiq.com/guides/utm-revenue-attribution) for campaign naming and [first-touch, last-touch and session-touch attribution](https://attribiq.com/guides/first-touch-vs-last-touch-attribution) before interpreting the channel totals.

## Validate the complete journey

Use a separate test project and Stripe test mode before connecting production payments. Keep your endpoint secret on the server and verify signatures against the unmodified request body, as described in [Stripe's webhook documentation](https://docs.stripe.com/webhooks).

1. Open a tagged campaign URL and verify its source, campaign and landing page in AttribIQ.
2. Complete a test checkout. Inspect its Checkout Session and subscription for the expected metadata keys.
3. Inspect the paid invoice webhook and confirm one payment record with the correct currency and amount. Check both the source report and unmatched revenue.
4. Resend the same event from Stripe. The recorded payment total must remain unchanged.
5. Exercise a renewal and a refund. Verify their object IDs and how they change the original journey's revenue.
6. Repeat with attribution unavailable. Payment ingestion should still work, with the payment shown as unmatched.

If you receive both Checkout and invoice events, choose one payment record for each billing transaction. An invoice and its Checkout Session can describe the same first charge; different webhook event IDs alone do not prove that two payments occurred.

## Diagnose missing attribution

| Symptom | First check |
| --- | --- |
| Payment is present but source is empty | Compare the metadata IDs with the actual tracked visitor and session |
| First payment matches but renewals do not | Confirm subscription metadata and inspect the renewal invoice payload |
| Revenue appears twice | Check both webhook redelivery handling and Checkout-versus-invoice overlap |
| Campaign differs from your expectation | Check the selected attribution model, UTM spelling and the observed journey |
| Stripe total differs from the report | Align dates, timezone, currencies, refund treatment and supported event types |

Do not repair missing evidence by guessing a source. Use the [unattributed revenue calculator](https://attribiq.com/tools/unattributed-revenue-calculator) to understand the size of the gap, then fix the checkout handoff for future payments.

## Next steps

Start with [passing attribution into Stripe Checkout](https://attribiq.com/guides/pass-attribution-to-stripe-checkout), then [connect Stripe to AttribIQ](https://attribiq.com/guides/connect-stripe-to-attribiq). If you do not use Stripe or need a custom relay, use the [generic Payment API guide](https://attribiq.com/guides/generic-payment-api-revenue-tracking).

## Frequently asked questions

### Which Stripe events should I start with?

For the native AttribIQ receiver, start with invoice.paid, charge.refunded, and customer.subscription.deleted. Add checkout.session.completed and customer.subscription.updated only after you confirm those events match your billing model.

### Should metadata live only on the Checkout Session?

No. Checkout metadata is useful for the first purchase, but subscription renewals and lifecycle changes need metadata on the subscription too.

### Can I use client_reference_id for attribution?

You can use it as a reconciliation pointer, but you should still copy explicit attribution identifiers into metadata so webhook processing is clear and extensible.

### What if some Stripe events arrive without attribution metadata?

Store and process them as unattributed revenue rather than dropping them. Missing attribution is a data quality issue, not a reason to lose revenue records.
