How to track Stripe revenue by source and campaign

By AttribIQUpdated Intermediate

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

Quick answer

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.

On this page
What you’ll learn
  • 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. Confirm that a visit appears in Realtime and that its traffic source and landing page 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. If you need purchases inside Google Analytics, use the separate Stripe and GA4 integration tutorial.

Stripe documents Checkout Sessions, metadata, and subscription webhooks in its official docs: Checkout Sessions, metadata, and subscription 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.

StepEvidence to retainExpected reporting outcome
A visitor lands on /pricing?utm_source=newsletter&utm_medium=email&utm_campaign=autumnSource, campaign, landing page, visitor and session IDsA newsletter visit in web analytics, with no revenue yet
The visitor signs up and starts CheckoutSignup event plus the same IDs on Checkout and subscription metadataA goal conversion and a checkout handoff
Stripe confirms the first €49 invoicePaid invoice ID, amount, currency and attribution metadataOne €49 revenue event assigned to the known journey
Stripe delivers that webhook againThe same Stripe event IDNo second revenue event
A later €49 renewal is paidA new invoice ID and the subscription's acquisition identifiersAnother €49, using the configured attribution model
€10 of that renewal is refundedRefund ID and a link to the original paymentA €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 for campaign naming and first-touch, last-touch and session-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.

  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

SymptomFirst check
Payment is present but source is emptyCompare the metadata IDs with the actual tracked visitor and session
First payment matches but renewals do notConfirm subscription metadata and inspect the renewal invoice payload
Revenue appears twiceCheck both webhook redelivery handling and Checkout-versus-invoice overlap
Campaign differs from your expectationCheck the selected attribution model, UTM spelling and the observed journey
Stripe total differs from the reportAlign dates, timezone, currencies, refund treatment and supported event types

Do not repair missing evidence by guessing a source. Use the 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, then connect Stripe to AttribIQ. If you do not use Stripe or need a custom relay, use the generic Payment API guide.

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.

Continue learning

Put it into practice

See traffic and revenue in the same dashboard

Explore traffic sources, campaigns, goals and matched payments in the demo, then connect your own site.