# Stripe Attribution in 2026: The Complete Guide to Connecting Payments to Marketing Channels

> Stripe attribution, the complete 2026 guide: join Stripe payments to marketing channels with webhook code, model trade-offs, and an honest tool comparison.

Author: Vincent Ruan (https://x.com/0xVinceAI)
Publisher: Attrifast — https://attrifast.com
Last updated: 2026-08-01
Canonical: https://attrifast.com/blog/stripe-attribution-complete-guide

**TL;DR**

- Stripe records the payment, plan, invoice, refund, and customer. It does not know which ad, article, search result, or AI answer brought the buyer to your site. You have to capture that context before checkout and pass it into Stripe metadata.
- Three webhooks cover most of the work: `checkout.session.completed` captures the conversion, `customer.subscription.created` captures activation, and `invoice.payment_succeeded` lets you report MRR by channel over time.
- For most bootstrapped SaaS teams, keep both first-touch and last-non-direct attribution. The first shows where the relationship began; the second shows which meaningful visit preceded the conversion.
- Entry prices range from $9.99/month for Attrifast to $499/month for Wicked Reports, with different audiences and depth. Shopify-heavy and enterprise teams need different tools from a small Stripe-native SaaS.
- Sigma can report MRR by marketing source only after your own tracker writes that source into Stripe metadata. Sigma handles the query; the capture pipeline supplies the missing data.
- **GA4 will not show you which channel drove your last $10k in Stripe MRR. [See the revenue-by-channel breakdown inside Attrifast](/dashboard) →** [Start free trial](/login)

A founder asked me for help with a board deck in March. Stripe showed $112,000 in MRR, and Sigma showed the plan mix. One slide was still blank: revenue by acquisition channel.

Marketing claimed the ChatGPT pipeline. The agency claimed LinkedIn. The content team claimed SEO. Stripe could not settle it, GA4 had lost part of the journey, and the BI tool had cleanly joined incomplete fields. The final slide said “directional, multi-touch.” Nobody trusted it.

This guide shows how to fill in that slide. The architecture is straightforward: capture the visit, carry an identifier into Checkout, handle the right webhooks, and write attribution into Stripe metadata. The harder part is choosing a model the team can explain and use consistently.

For AI-specific traffic detection, use the [ChatGPT referral analytics guide](/blog/chatgpt-referral-analytics-guide). [AEO vs SEO](/blog/aeo-vs-seo-2026) explains the broader channel strategy, and [does GEO actually drive revenue](/blog/does-geo-actually-drive-revenue) covers the evidence behind AI-driven channels. Here, the focus is Stripe: webhooks, metadata, joins, and attribution models.

![Stripe attribution gap: Stripe captures every payment dimension (plan, country, MRR, ARR, refund, dispute) but no marketing channel field exists on any Stripe object unless you write it into metadata yourself](/blog/stripe-attribution-complete-guide/hero.svg)

> **Updated August 2026.** Prices changed sharply since the May edition. The underlying Stripe implementation changed very little.
>
> **Pricing:** I re-checked every entry price against the vendor's site on 31 July 2026. SegMetrics replaced its $175/month Essentials tier with Launch at $57. Wicked Reports moved from a $249 Foundation plan to a $499 Measure plan. Triple Whale added a free tier and moved its first paid tier to $179. Baremetrics, ChartMogul, Northbeam, and Stripe Sigma also changed their packaging. Sigma now charges by successful monthly payments rather than events, making DIY reporting cheaper for subscription businesses with relatively few charges.
>
> **Implementation:** `client_reference_id`, Checkout Session metadata, the 50-key and 500-character metadata limits, the core webhooks, and the three-day retry window are unchanged. Stripe now calls webhook endpoints “event destinations” in Workbench and calls test mode “sandboxes.” It also recommends restricted API keys for new integrations. Stripe no longer publishes a fixed 30-second handler timeout; the guidance is to return 2xx before slow work. This edition also adds Payment Links.

## Quick Facts

| Metric | Value | Source |
|---|---|---|
| Stripe processed payment volume (FY2024) | Roughly $1.4 trillion | Stripe annual letter [1] |
| Stripe webhook event types available | 200+ | Stripe webhook docs [2] |
| Webhook events that matter for attribution | 3 core, ~6 with refunds/churn | Author's analysis |
| Stripe Sigma availability | All paid Stripe accounts | Stripe Sigma docs [3] |
| Stripe Customer metadata key limit | 50 keys per object, 40-char key names, 500 chars per value | Stripe API reference [4] |
| Median time from trial start to paid conversion (B2B SaaS, 2025) | 11-18 days | OpenView SaaS benchmarks [5] |
| Median trial-to-paid conversion rate (B2B SaaS, 2025) | 14-23% | ChartMogul benchmark [6] |
| % of SMB SaaS using GA4 as primary attribution | Roughly 71% | Baremetrics blog survey [7] |
| % of those who can name their top revenue-driving channel correctly | Roughly 38% | Author's audit, n=42 |
| SegMetrics entry price (Jul 2026) | $57/mo Launch | SegMetrics pricing page [8] |
| Wicked Reports entry price (Jul 2026) | $499/mo Measure | Wicked Reports pricing [9] |
| TripleWhale entry price (Jul 2026) | $179/mo Starter, free tier below it (Shopify-first) | TripleWhale pricing [10] |
| Polar Analytics entry price (Jul 2026) | GMV-quoted, roughly $300+/mo | Polar pricing page [11] |
| Baremetrics entry price (Jul 2026) | $75/mo Launch ($49 annual) | Baremetrics pricing [12] |
| Northbeam entry price (Jul 2026) | $1,500/mo Starter (published) | Northbeam pricing [13] |
| Attrifast entry price (Jul 2026) | $9.99/mo Starter, $49/mo Pro | Attrifast pricing |
| Median ROI of attribution investment, per McKinsey | 15-20% lift in marketing efficiency | McKinsey on marketing analytics [14] |

Two numbers frame the problem. Stripe exposes more than 200 webhook event types, giving you detailed financial events across the customer lifecycle. Yet only 38% of operators in my audit could name their top revenue-driving channel correctly. The payment data exists; the acquisition context is missing.

## Why Stripe alone cannot tell you where revenue came from

Stripe knows what happened after the buyer reached the payment flow. It does not see the visit that came before it. Customer, Subscription, Invoice, Charge, PaymentIntent, and Checkout Session objects all accept custom `metadata`, subject to a 50-key limit and 500 characters per value [4]. Stripe stores the attribution fields you send, but it cannot create them from a web session it never observed.

A useful mental model is simple: Stripe is the ledger; your analytics system provides the route to the ledger. Attribution joins the two.

Here is the side-by-side of what Stripe knows about a typical paying customer versus what you need to know for attribution:

| Dimension | Stripe knows it natively? | Where it lives | What you need to add |
|---|---|---|---|
| Customer email | Yes | `Customer.email` | Nothing |
| Plan and price | Yes | `Subscription.items[].price.id` | Nothing |
| Currency, country, tax | Yes | `Customer.address`, `Invoice.tax` | Nothing |
| MRR per customer | Computable | `Subscription.items[].price.unit_amount` | Nothing |
| Lifetime value | Computable | Sum of `Invoice.amount_paid` | Nothing |
| Refunds, disputes, chargebacks | Yes | `Charge.refunded`, `Dispute` | Nothing |
| First-touch marketing channel | **No** | None | Must write to metadata yourself |
| Last-touch marketing channel | **No** | None | Must write to metadata yourself |
| UTM source/medium/campaign | **No** | None | Must capture upstream and pass through |
| Landing page | **No** | None | Must capture upstream and pass through |
| Referrer | **No** | None | Must capture upstream and pass through |
| Session duration before purchase | **No** | None | Must capture upstream |
| Number of pageviews before purchase | **No** | None | Must capture upstream |
| AI engine source (ChatGPT, Perplexity, etc.) | **No** | None | Must capture upstream via referer fingerprinting |
| Ad campaign ID (Meta, Google, LinkedIn) | **No** | None | Must capture from URL params, pass through metadata |
| Coupon attribution | Partial | `Customer.discount.coupon.id` | Useful as a proxy for some campaigns |
| Affiliate / partner attribution | **No** | None | Must implement via metadata convention |
| Sales rep attribution | **No** | None | Manual write at customer.created |
| Trial source (organic vs paid) | **No** | None | Must capture upstream |
| Activation channel (different from acquisition) | **No** | None | Custom metric, must derive |

The lower half of the table contains the fields that guide marketing budgets, and Stripe does not collect any of them. Capture those signals on your site and pass a summary into metadata, or the payment record will have no channel context.

Stripe Atlas publishes useful articles about marketing attribution [15], but Stripe does not offer a product called “Stripe Attribution.” Sigma is the closest native reporting tool. It can query Stripe's own fields and any metadata you add, but it cannot capture the original visit. You still need a tracker and a way to carry its identifier into Stripe.

| Stripe primitive | What it does | Helpful for attribution? |
|---|---|---|
| Dashboard | Manual UI for inspecting customers, charges | Read-only, not built for channel analysis |
| Reports (in Dashboard) | Pre-built financial reports | Pre-built, no attribution dimension |
| Sigma | SQL over Stripe data | Yes if metadata is populated; useless otherwise |
| Data Pipeline | Streams Stripe data to your warehouse | Building block, requires custom ETL |
| Workflows | Visual automation | Can write to metadata; not a capture engine |
| Customer Portal | Self-serve subscription UI | Outside attribution scope |
| Apps | Stripe-built integrations | Marketplace varies; few attribution apps |
| Webhooks | Real-time event stream | Yes, this is where capture happens |
| Metadata fields | Open key-value storage | The destination for attribution data |

Stripe provides the building blocks. Your application — or an attribution product — has to connect them.

## The three Stripe webhook events that matter for attribution

Stripe has more than 200 event types [2], but three cover most SaaS and ecommerce attribution. Two more handle refunds and churn. You may need other events elsewhere in the product, but not for a basic channel report.

### The core three

**Event 1: `checkout.session.completed`.** This fires when Checkout completes. The session contains the metadata set when it was created, so the handler can read the first-party visitor ID and copy attribution into Customer and Subscription metadata. For ecommerce, it is also a natural point to record the order's channel.

**Event 2: `customer.subscription.created`.** This marks the start of a subscription. In a trial flow, later subscription updates may mark the move to paid; without a trial, creation often occurs near checkout completion. Keep it separate because the channel that brought the signup may differ from the one that drove activation.

**Event 3: `invoice.payment_succeeded`.** This fires for successful invoice payments, including renewals. Add each event to the customer's acquisition channel to build MRR and lifetime value by channel. Without renewals, you only know the value of the first payment.

The webhook payload shape, at a glance:

| Event type | Triggers when | Key payload fields for attribution | Idempotency notes |
|---|---|---|---|
| `checkout.session.completed` | A Checkout Session completes | `id`, `customer`, `subscription`, `metadata`, `client_reference_id`, `payment_intent` | Use session `id` as idempotency key |
| `customer.subscription.created` | New Subscription begins | `id`, `customer`, `metadata`, `items`, `status`, `trial_end` | Use subscription `id` as idempotency key |
| `invoice.payment_succeeded` | Recurring or one-off invoice paid | `id`, `customer`, `subscription`, `amount_paid`, `billing_reason` | Use invoice `id` as idempotency key |
| `customer.created` | New Customer object created | `id`, `metadata`, `email` | Use customer `id` as idempotency key |
| `customer.subscription.updated` | Subscription plan/status changes | `id`, `customer`, `previous_attributes`, `status` | Use `(id, occurred_at)` for idempotency |
| `customer.subscription.deleted` | Subscription canceled | `id`, `customer`, `canceled_at` | Use subscription `id` |
| `charge.refunded` | Refund issued | `id`, `customer`, `amount_refunded` | Use charge `id` plus refund `id` |
| `invoice.payment_failed` | Recurring payment fails | `id`, `customer`, `attempt_count` | Use invoice `id` |
| `customer.updated` | Customer email/address/metadata changed | `id`, `previous_attributes` | Use `(id, occurred_at)` for idempotency |
| `payment_intent.succeeded` | One-off payment completes (non-Checkout) | `id`, `customer`, `metadata`, `amount` | Use payment intent `id` |

### The two edge-case events

**Event 4: `charge.refunded`.** For ecommerce attribution, refund rates vary enormously by channel. Paid social drives higher refund rates than organic search at most DTC sites; ignoring refunds inflates paid social ROI by 10-30%. Subtracting refunded amount from the original channel's revenue gives a more honest channel ROI.

**Event 5: `customer.subscription.deleted` / `customer.subscription.updated`.** For SaaS, churn-by-channel is the second-most-important question after acquisition-by-channel. The combination of these two events lets you compute net-MRR-by-channel, which is the metric that actually moves budget decisions long-term.

### A full webhook event map for attribution

| Webhook event | Frequency at typical SMB SaaS | Required for basic attribution? | Required for advanced attribution? |
|---|---|---|---|
| `checkout.session.completed` | High (every conversion) | Yes | Yes |
| `customer.subscription.created` | Medium | Recommended | Yes |
| `invoice.payment_succeeded` | Highest (every renewal) | Yes (for MRR over time) | Yes |
| `customer.created` | Medium | Optional | Yes (for first-touch lock-in) |
| `customer.subscription.updated` | Medium | Optional | Yes (for expansion attribution) |
| `customer.subscription.deleted` | Low | Optional | Yes (for churn-by-channel) |
| `charge.refunded` | Low-medium | Optional | Yes (for ecommerce net-rev) |
| `invoice.payment_failed` | Low-medium | No | Useful (for dunning attribution) |
| `payment_intent.succeeded` | Variable | Yes if not using Checkout | Yes |
| `payout.paid` | Daily/weekly | No | Useful for cash-basis reconciliation |

For a new SaaS implementation, start with `checkout.session.completed`, `customer.subscription.created`, and `invoice.payment_succeeded`. Make each handler idempotent and write first-touch plus last-touch to Customer metadata. Add churn and refund events once those distinctions matter to your decisions.

## A worked example: Node.js webhook handler that writes attribution

The example below is a minimal idempotent Express and Node.js handler. The same sequence works in Fastify, Hono, Next.js route handlers, FastAPI, or Rails.

```javascript
// webhooks/stripe.js — minimal Stripe attribution capture handler

import express from 'express'
import Stripe from 'stripe'
import { db } from '../db.js'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET

export const router = express.Router()

router.post(
  '/webhooks/stripe',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    let event
    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        req.headers['stripe-signature'],
        endpointSecret,
      )
    } catch (err) {
      return res.status(400).send(`Webhook signature failed: ${err.message}`)
    }

    // Idempotency: skip if we have already processed this event
    const alreadyProcessed = await db.processedEvents.findById(event.id)
    if (alreadyProcessed) return res.status(200).send('ok-duplicate')

    try {
      switch (event.type) {
        case 'checkout.session.completed':
          await handleCheckoutCompleted(event.data.object)
          break
        case 'customer.subscription.created':
          await handleSubscriptionCreated(event.data.object)
          break
        case 'invoice.payment_succeeded':
          await handleInvoicePaid(event.data.object)
          break
        case 'customer.created':
          await handleCustomerCreated(event.data.object)
          break
      }
      await db.processedEvents.insert({ id: event.id, processedAt: new Date() })
      res.status(200).send('ok')
    } catch (err) {
      // Stripe will retry on 5xx, so we surface the failure
      console.error('webhook handler error', err)
      res.status(500).send('handler-failed')
    }
  },
)

async function handleCheckoutCompleted(session) {
  // 1. Pull the first-party identifier from session metadata (we set it on create)
  const firstPartyId = session.metadata?.first_party_id
  if (!firstPartyId) return // pre-attribution checkout, no upstream session

  // 2. Look up the upstream session row written by the tracker
  const upstreamSession = await db.sessions.findFirstPartyId(firstPartyId)
  if (!upstreamSession) return

  // 3. Compute first-touch and last-non-direct touch
  const touches = await db.touches.findByFirstPartyId(firstPartyId)
  const firstTouch = touches[0]
  const lastNonDirectTouch =
    [...touches].reverse().find((t) => t.channel !== 'direct') ?? firstTouch

  // 4. Write attribution into Customer metadata (immutable forever)
  if (session.customer) {
    await stripe.customers.update(session.customer, {
      metadata: {
        attr_first_channel: firstTouch.channel,
        attr_first_source: firstTouch.source ?? '',
        attr_first_campaign: firstTouch.campaign ?? '',
        attr_first_at: firstTouch.occurredAt.toISOString(),
        attr_last_channel: lastNonDirectTouch.channel,
        attr_last_source: lastNonDirectTouch.source ?? '',
        attr_last_campaign: lastNonDirectTouch.campaign ?? '',
        attr_last_at: lastNonDirectTouch.occurredAt.toISOString(),
      },
    })
  }

  // 5. Also stamp the conversion row in our own DB for fast joins later
  await db.conversions.insert({
    stripeCustomerId: session.customer,
    stripeSessionId: session.id,
    firstPartyId,
    firstChannel: firstTouch.channel,
    lastNonDirectChannel: lastNonDirectTouch.channel,
    amountPaid: session.amount_total,
    currency: session.currency,
    occurredAt: new Date(session.created * 1000),
  })
}

async function handleSubscriptionCreated(subscription) {
  // Capture activation-time attribution distinct from signup-time
  const customer = await stripe.customers.retrieve(subscription.customer)
  const firstChannel = customer.metadata?.attr_first_channel ?? 'unknown'
  await stripe.subscriptions.update(subscription.id, {
    metadata: {
      attr_activation_channel: firstChannel,
      attr_activation_at: new Date().toISOString(),
    },
  })
}

async function handleInvoicePaid(invoice) {
  // Accumulate MRR-by-channel for over-time reporting
  const customer = await stripe.customers.retrieve(invoice.customer)
  const channel = customer.metadata?.attr_last_channel ?? 'unknown'
  await db.mrrEvents.insert({
    stripeCustomerId: invoice.customer,
    stripeInvoiceId: invoice.id,
    channel,
    amount: invoice.amount_paid,
    currency: invoice.currency,
    billingReason: invoice.billing_reason,
    occurredAt: new Date(invoice.created * 1000),
  })
}

async function handleCustomerCreated(customer) {
  // No-op for most cases; useful if you want to lock the first-touch immediately
  // and not wait for checkout.session.completed
}
```

Before this reaches production, check the following:

| Check | Why it matters | Common failure |
|---|---|---|
| Signature verification on every request | Without it, attribution data is forgeable by anyone with your URL | Skipped during local testing, never re-enabled |
| Raw body used for signature, not parsed JSON | Stripe signs the raw bytes | Body parsed by Express middleware before reaching verifier |
| Idempotency table keyed on `event.id` | Stripe retries on 5xx and timeouts | Handler treats each retry as new event |
| Returns 2xx before any slow work | Stripe marks slow deliveries "timed out" and retries them | Long-running sync work ahead of the response |
| Returns 200 quickly for unhandled event types | Avoids unnecessary retries | Returns 500 for events you don't care about |
| Wraps Stripe API calls in retry-on-rate-limit | Stripe API can rate-limit at burst | Single failed update silently drops attribution |
| Logs the event ID and type on entry | Debugging requires the event ID | Logs without IDs are unparseable |
| Stores raw event payload for replay | Lets you reprocess after handler bugs | Replay impossible if payload is gone |
| First-touch is write-once, not overwrite | Customer.metadata is mutable from Stripe's side | Last touch silently replaces first touch on update |
| Sandbox and live mode events routed separately | Mixing modes corrupts attribution | Single endpoint handles both, attribution leaks across |
| Restricted API key, not a secret key | Stripe now recommends RAKs over secret keys for new integrations | Full-access `sk_live_` key sitting in an env var |

**Always verify the signature.** Stripe's [webhook security docs](https://docs.stripe.com/webhooks#verify-events) [16] require verification before you act on an event. Verify against the raw request body, not parsed JSON. Otherwise, anyone who finds the endpoint could forge attribution updates. You can add another layer by restricting the endpoint to [Stripe's published webhook IP ranges](https://docs.stripe.com/ips).

**Current Stripe terminology.** The console now calls a webhook endpoint an **event destination**, configured under Webhooks in **Workbench** [28]. “Test mode” is now “sandboxes.” The payload, `Stripe-Signature` header, library methods, and test-key prefixes are unchanged.

**Use a restricted API key.** Stripe advises new integrations to use restricted keys (`rk_live_`) because secret keys cannot be scoped [27]. Attribution needs read access to Checkout Sessions and Invoices plus write access to Customer and Subscription metadata. It does not need refunds, payouts, or Connect. Scope the key to those operations and, if possible, restrict where it can be used.

**Make the handler idempotent.** Stripe guarantees at-least-once delivery, so the same event may arrive again. A 500 response can trigger retries for up to three days in live mode. Store the `event.id` after a successful run and check it at the start of the handler. Without that guard, a retry can duplicate revenue or overwrite attribution.

**Return quickly.** Stripe no longer gives a fixed timeout in its guidance. Verify the signature, enqueue the event, return 2xx, and perform slower Stripe API writes in a worker. A slow synchronous handler risks a timeout and another delivery.

**Protect first-touch in your own code.** Stripe lets any later update overwrite Customer metadata. Check `customer.metadata.attr_first_channel` before writing it again. The sample omits that branch for brevity.

**Keep metadata small.** Stripe allows 50 keys and 500 characters per value [4]. Store the channel summary and join key in Stripe; keep the full visit history in your own database.

## A worked SQL example: revenue by channel from your Stripe + session join

Once Customer metadata and session rows contain the attribution keys, revenue by channel is a straightforward join. This PostgreSQL example assumes a local `stripe_customers` mirror plus your own `sessions` and `mrr_events` tables.

```sql
-- Revenue by acquisition channel, current quarter, last-non-direct attribution
SELECT
  sc.metadata->>'attr_last_channel' AS channel,
  COUNT(DISTINCT sc.id) AS paying_customers,
  SUM(me.amount) / 100.0 AS revenue_usd,
  SUM(me.amount) / 100.0 / NULLIF(COUNT(DISTINCT sc.id), 0) AS arpu_usd
FROM stripe_customers sc
JOIN mrr_events me ON me.stripe_customer_id = sc.id
WHERE me.occurred_at >= date_trunc('quarter', now())
  AND me.occurred_at < date_trunc('quarter', now()) + INTERVAL '3 months'
GROUP BY 1
ORDER BY revenue_usd DESC;
```

The same shape with first-touch attribution swapped in is a single column change. Sigma syntax differs slightly but the pattern is identical.

The two queries side by side often produce dramatically different channel rankings:

| Channel | Last-non-direct revenue | First-touch revenue | Delta |
|---|---|---|---|
| Google organic | $42,180 | $31,440 | -$10,740 |
| ChatGPT / AI | $14,180 | $19,840 | +$5,660 |
| Email | $11,820 | $4,200 | -$7,620 |
| Paid social | $9,310 | $8,640 | -$670 |
| Direct | $0 (excluded) | $11,180 | +$11,180 |
| Referral | $4,210 | $3,920 | -$290 |
| Affiliate | $2,140 | $2,440 | +$300 |
| Total | $83,840 | $81,660 | -- |

(Totals differ slightly because first-touch attributes some revenue to channels for which the last touch was excluded; last-non-direct re-routes those dollars to the prior non-direct touch.)

The two columns tell different stories. ChatGPT and Direct are larger on first-touch because they often begin the journey. Email and Google are larger on last-non-direct because they more often precede conversion. Choose the column that matches the decision, and keep both visible.

Rendered rather than queried, the same join looks like this — each channel carrying the dollars its customers actually paid:

![Attrifast dashboard showing Stripe revenue split by channel — ChatGPT, Google, Perplexity, Claude and Direct each on their own line with dollar amounts — alongside prompt-level AI visibility and competitor position tracking.](/product/ai-visibility-features.webp)

A complementary query that often surfaces actionable findings: channel-by-plan-tier, to see whether your acquisition channels map onto your revenue tiers cleanly. The plan ladder below belongs to the anonymized example SaaS, not to Attrifast:

| Channel | Basic ($29/mo) | Plus ($79/mo) | Business ($199/mo) | Enterprise (custom) | Weighted ARPU |
|---|---|---|---|---|---|
| Google organic | 142 | 64 | 18 | 2 | $61 |
| ChatGPT | 38 | 24 | 8 | 1 | $69 |
| Perplexity | 6 | 9 | 5 | 1 | $98 |
| Email | 84 | 31 | 7 | 0 | $52 |
| Paid social | 71 | 22 | 4 | 0 | $46 |
| Referral / partner | 12 | 18 | 12 | 3 | $112 |
| Affiliate | 22 | 6 | 1 | 0 | $42 |
| Direct | 38 | 12 | 4 | 1 | $58 |

Here, referrals and Perplexity bring more higher-tier customers, while affiliates and paid social lean toward Starter. That is why revenue by channel and average revenue per user answer different questions. A high-volume channel can look strong in total signups and weak in CAC payback.

## The attribution model question: first-touch vs last-touch for SaaS

No attribution model is the truth. Each one assigns credit according to a rule, and each rule has a blind spot. Choose the model based on the length of your funnel and the decision the report needs to support.

| Model | What it credits | Strength | Blind spot |
|---|---|---|---|
| First-touch | The channel that started the journey | Captures discovery; rewards content and SEO | Ignores the channel that actually drove conversion; over-rewards top-of-funnel |
| Last-touch | The channel of the final session before conversion | Captures conversion; rewards email and paid ads | Over-credits direct re-visits and brand recall; ignores discovery |
| Last-non-direct touch | The last non-Direct channel before conversion | Best balance for most SaaS; preserves AI/organic signal without over-crediting bookmarks | Still ignores middle-of-funnel touches |
| Linear | Equal credit to every touch | Honest about multi-touch; no channel over-rewarded | Equal-credit is itself an assumption; rewards channels with high touch count |
| Time-decay | More credit to recent touches | Captures conversion bias without ignoring discovery entirely | Decay rate is arbitrary; small change shifts rankings |
| Position-based (U-shaped) | 40% first, 40% last, 20% middle | Pragmatic compromise; captures both endpoints | Middle channels under-credited |
| Data-driven (Markov, Shapley) | Computed by algorithm | Theoretically best; rewards real causality | Requires high data volume; opaque; rankings shift with re-training |

For most bootstrapped SaaS teams, I recommend **last-non-direct touch alongside a first-touch column**. One shows the last meaningful channel before conversion; the other preserves the source of discovery. A dashboard that can switch between them is more useful than a single supposedly definitive number.

The decision tree I use in customer onboarding:

```mermaid
flowchart TD
  A[What decision is the attribution supporting?] --> B{Budget allocation for next quarter?}
  A --> C{Content ROI measurement?}
  A --> D{Sales rep / SDR credit?}
  A --> E{Board reporting / investor update?}
  B --> F[Use last-non-direct + first-touch dual columns; review both]
  C --> G[Use first-touch; content's job is discovery]
  D --> H[Use last-touch; rep gets credit for closing]
  E --> I[Use linear or position-based; investors expect 'multi-touch']
```

Document which model supports each decision. Most SaaS teams need two or three views side by side; otherwise every weekly review turns into the same argument about whether email or content received too much credit.

A worked example from a real Attrifast customer, with the channels and dollars anonymized but the shape preserved:

| Channel | Sessions (qtr) | First-touch revenue | Last-touch revenue | Last-non-direct revenue | Linear revenue |
|---|---|---|---|---|---|
| Google organic | 48,200 | $58,400 | $34,210 | $51,820 | $46,180 |
| ChatGPT | 9,180 | $24,710 | $8,420 | $19,310 | $17,610 |
| Perplexity | 2,140 | $11,820 | $2,140 | $9,420 | $7,820 |
| Direct | 12,400 | $4,210 | $44,180 | $0 (excluded) | $14,210 |
| Email | 8,420 | $3,840 | $28,140 | $25,820 | $18,420 |
| Paid social | 14,200 | $14,210 | $11,820 | $13,640 | $12,820 |
| Affiliate | 1,840 | $2,820 | $4,210 | $4,180 | $3,840 |
| Total | -- | $120,010 | $133,120 | $124,190 | $120,900 |

The table highlights four patterns.

**Direct dominates last-touch.** It receives $44,180, or 33% of the total. Most of those buyers discovered the brand elsewhere and returned through a bookmark or typed URL. The label is technically correct for the final visit but offers little guidance because you cannot buy more Direct traffic.

**ChatGPT and Perplexity are much larger on first-touch.** ChatGPT's first-touch revenue is 2.9 times its last-touch revenue; Perplexity's is 5.5 times. In this example, AI starts journeys more often than it closes them. A last-touch-only report makes the channel look 3-5 times smaller.

Plotted side by side, the model choice stops being an abstraction: the same quarter of Stripe revenue reorders almost completely depending on which column you report.

**First-touch vs last-touch revenue by channel (one quarter, anonymized SaaS)**

| Category | First-touch revenue | Last-touch revenue |
|---|---|---|
| Google organic | $58400 | $34210 |
| ChatGPT | $24710 | $8420 |
| Perplexity | $11820 | $2140 |
| Direct | $4210 | $44180 |
| Email | $3840 | $28140 |
| Paid social | $14210 | $11820 |
| Affiliate | $2820 | $4210 |

*Source: Attribution-model comparison table in this article — anonymized Attrifast customer, one quarter; Total row omitted*

**Email shows the reverse.** Its last-touch revenue is 7.3 times its first-touch revenue. Email often closes a journey that began elsewhere, so first-touch understates it and last-touch overstates it.

**Linear and last-non-direct are similar at this scale.** They rank some channels differently, but the totals stay in the same range. For an SMB SaaS, that difference may be smaller than the measurement error in the underlying visits.

## Setting up Stripe-to-attribution in four steps

This is the architecture used in Attrifast, and it also works for a custom build. The pieces are familiar — a first-party ID, a session table, Stripe metadata, and an idempotent webhook — and an experienced backend engineer can usually assemble the basic version in about two days.

The four steps mapped to ownership, tools, and gotchas:

| Step | Owner | Primary tool | Common gotcha |
|---|---|---|---|
| Establish first-party identifier | Front-end engineer | localStorage / first-party cookie | Cleared by browser extensions, breaks identity |
| Capture session and touch data | Back-end engineer | Your own API + DB | UTM tag inconsistency across teams pollutes channel data |
| Pass ID into Checkout Session | Full-stack | Stripe Checkout SDK | ID dropped if checkout opened in new tab without referer state |
| Webhook handler writes attribution | Back-end engineer | Stripe webhooks | Non-idempotent handler double-writes on retry |

### Step 1: Establish a first-party identifier on the visitor

Generate a UUID on the first visit, store it in `localStorage` or a first-party cookie, and attach it to later analytics events. This ID links the web session to the Stripe payment.

```javascript
// tracker.js — first-party identifier setup
function getOrCreateFirstPartyId() {
  let id = localStorage.getItem('_afpid')
  if (!id) {
    id = crypto.randomUUID()
    localStorage.setItem('_afpid', id)
  }
  return id
}
```

The identifier stays on your domain and behaves more like a login-session ID than a cross-site advertising cookie. Many jurisdictions treat those differently, but confirm the consent requirements with your privacy review.

A reference channel-mapping table covering the common URL parameters and referer patterns the inferred_channel column should resolve to:

| Signal | Pattern | Inferred channel | Notes |
|---|---|---|---|
| `utm_source=google&utm_medium=cpc` | UTM | paid-search | Google Ads click |
| `utm_source=google&utm_medium=organic` | UTM | organic-search | Manual organic tag (rare) |
| `gclid=...` | URL param | paid-search | Google Click ID |
| `fbclid=...` | URL param | paid-social | Facebook/Meta Click ID |
| `li_fat_id=...` | URL param | paid-social | LinkedIn campaign ID |
| `ttclid=...` | URL param | paid-social | TikTok Click ID |
| `msclkid=...` | URL param | paid-search | Microsoft Ads Click ID |
| Referer `google.com` (no UTM) | Referer | organic-search | Google SERP click |
| Referer `bing.com` | Referer | organic-search | Bing organic |
| Referer `chatgpt.com` / `chat.openai.com` | Referer | ai-search | ChatGPT citation |
| Referer `perplexity.ai` | Referer | ai-search | Perplexity citation |
| Referer `claude.ai` | Referer | ai-search | Claude citation |
| Referer `gemini.google.com` | Referer | ai-search | Gemini citation |
| Referer `linkedin.com` | Referer | social | LinkedIn organic |
| Referer `t.co` / `x.com` / `twitter.com` | Referer | social | X / Twitter |
| Referer `reddit.com` | Referer | social-community | Reddit |
| Referer `news.ycombinator.com` | Referer | social-community | Hacker News |
| Referer `producthunt.com` | Referer | social-community | Product Hunt |
| `utm_medium=email` | UTM | email | Email campaign |
| Referer `mail.google.com` / `outlook.live.com` | Referer | email (webmail) | Webmail client |
| No referer, no UTM, root URL | Empty | direct | True direct or bookmark |
| No referer, deep URL, FAQ-shaped page | Behavioral | suspected-ai | Likely AI citation click |

### Step 2: Capture session and touch data on your server

Send each pageview and meaningful event to your server. Store one row per visit in `sessions` and one row per channel-bearing event in `touches`. A minimal session schema looks like this:

| Column | Type | Notes |
|---|---|---|
| id | UUID | Primary key |
| first_party_id | UUID | Join key from the tracker |
| started_at | timestamptz | First event in session |
| ended_at | timestamptz | Last event in session (rolling) |
| utm_source | text | From URL params, nullable |
| utm_medium | text | From URL params, nullable |
| utm_campaign | text | From URL params, nullable |
| utm_content | text | From URL params, nullable |
| utm_term | text | From URL params, nullable |
| referer | text | document.referrer or HTTP Referer |
| referer_host | text | Parsed hostname for fast filtering |
| landing_page | text | First URL in session |
| user_agent | text | For bot exclusion |
| ip_country | text | Geo-IP at session start (no IP stored) |
| device_type | text | mobile/desktop/tablet |
| inferred_channel | text | Computed at write time (organic/paid/ai/email/direct) |

`inferred_channel` maps the UTM fields, referrer host, and behavioral signals to your channel names. Computing it when you write the visit keeps later dashboard queries simple and fast.

### Step 3: Pass the first-party ID into the Stripe Checkout Session

When you create the Checkout Session, add the first-party ID to its metadata. That field connects your web tracking record to Stripe.

```javascript
// server: create checkout session with attribution carrier
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  success_url: 'https://yourapp.com/welcome',
  cancel_url: 'https://yourapp.com/pricing',
  metadata: {
    first_party_id: req.body.firstPartyId, // from client, validated
  },
  client_reference_id: req.body.firstPartyId, // alternate field, redundant by design
})
```

Use `metadata.first_party_id` as the main carrier. I also write the same value to Stripe's `client_reference_id` [17] because it appears in more places in the Dashboard. Either field is enough; using both adds a simple fallback.

For Stripe Billing customers who do not use Checkout (direct PaymentIntents, embedded forms, etc.) the same pattern applies: write the first-party ID into PaymentIntent metadata or Customer metadata at create time. The principle is the same regardless of integration shape: the first-party ID must travel with the checkout artifact so the webhook handler can join it back to a session.

#### The Payment Links case

Payment Links are different because the checkout URL is static; your server does not create a new Session when someone clicks. Stripe provides two ways to carry attribution through that flow [26].

First, a Payment Link can store fixed metadata on the link, PaymentIntent, or Subscription it creates. Use this for campaign constants. A link made for the March newsletter can carry `attr_campaign: "march-newsletter"`, and every resulting payment inherits it.

Second, append the visitor ID as `client_reference_id` in the URL. Stripe copies it to the resulting Checkout Session, where the same webhook handler can read it. Keep the value to letters, numbers, dashes, or underscores and under 200 characters; a UUID fits. Stripe also carries the five standard UTM parameters, up to 150 characters each.

```javascript
// client: decorate a Payment Link with the visitor's first-party ID
const url = new URL('https://buy.stripe.com/xxxxxxxx')
url.searchParams.set('client_reference_id', getOrCreateFirstPartyId())
window.location.href = url.toString()
```

The trade-off is simple. Fixed link metadata tracks the campaign without JavaScript and survives when someone forwards the URL. `client_reference_id` connects the full visitor history, but only when your tracker ran before the click.

### Step 4: Webhook handler joins the conversion to the session and writes attribution

The webhook now reads the first-party ID, finds the matching visits, calculates first-touch and last-non-direct channels, and writes the summary to Customer metadata. Future Stripe reports can use those fields directly.

The full four-step pipeline at a glance:

```mermaid
sequenceDiagram
  participant V as Visitor
  participant T as Tracker (browser)
  participant S as Your Server
  participant ST as Stripe
  participant W as Webhook Handler
  V->>T: Visits page
  T->>T: Get or create first-party UUID
  T->>S: POST /track {first_party_id, utm, referer}
  S->>S: Insert sessions row, touches row
  V->>V: Browses, eventually clicks Checkout
  T->>S: POST /checkout {first_party_id}
  S->>ST: stripe.checkout.sessions.create({metadata:{first_party_id}})
  ST->>V: Hosted checkout
  V->>ST: Pays
  ST->>W: webhook checkout.session.completed
  W->>S: Look up session by first_party_id
  W->>ST: stripe.customers.update({metadata:{attr_first_channel, attr_last_channel}})
  W->>S: Insert conversions row
```

An experienced backend engineer can usually build the basic flow in 1-2 days and roughly 300 lines across the tracker, API, schema, and webhook. Most of that time goes into edge cases: duplicate Checkout sessions, refunds and repeat payments, cleared identifiers, and purchases completed on another device.

## Tool comparison: Stripe-native attribution tools in 2026

The main options range from a DIY build to tools costing more than $1,500 per month. The best fit depends on your business model, paid-ad spend, and whether a data warehouse and data team already exist.

| Tool | Best for | Native Stripe? | E-comm focus? | SaaS focus? | Cookieless? | Entry price | Free trial? |
|---|---|---|---|---|---|---|---|
| Attrifast | SMB SaaS / DTC with under $100k/mo ad spend | Yes (restricted API key + webhooks) | Yes | Yes | Yes | $9.99/mo Starter, $49/mo Pro | 7 days |
| SegMetrics | Mid-market info-product and SaaS | Yes (via integrations) | Limited | Yes | No (uses pixels) | $57/mo Launch [8] | 14 days |
| Wicked Reports | E-comm and info-product with paid ad heavy mix | Yes | Yes | Limited | No | $499/mo Measure [9] | No |
| TripleWhale | Shopify DTC with $50k+/mo paid social spend | Indirect (via Stripe app) | Yes (Shopify-first) | No | No | Free tier, then $179/mo Starter [10] | Free tier |
| Polar Analytics | Shopify mid-market | Indirect | Yes (Shopify-only) | No | No | GMV-quoted, ~$300+/mo [11] | No |
| Baremetrics | Stripe SaaS metrics dashboard | Yes (deep) | No | Yes | n/a (no tracker) | $75/mo Launch [12] | 14 days |
| ChartMogul | Stripe SaaS metrics with cohort depth | Yes | No | Yes | n/a (no tracker) | Free under $10k MRR, then $59/mo [18] | Free tier |
| ProfitWell (now Paddle Retain) | Free SaaS metrics; subscription analytics | Yes (Stripe-friendly) | No | Yes | n/a | Free tier | n/a |
| Northbeam | Enterprise multi-touch attribution | Yes | Yes | Limited | Partial | $1,500/mo Starter [13] | No |
| DIY: Sigma + custom tracker | Engineering teams with capacity | Yes (Sigma is Stripe-native) | Both | Both | Yes | Sigma from $15/mo by charge volume [3] | 30 days Sigma |
| DIY: BigQuery + Segment | Mid-market with existing warehouse | Via Stripe Data Pipeline | Both | Both | Yes (depending) | $120+/mo (Segment) | n/a |
| GA4 free + Stripe export | Operators on shoestring budget | Manual | Both | Both | No | $0 + time | n/a |

I re-checked every public entry price on **31 July 2026**. Eight of the twelve rows changed since May. Polar no longer publishes a flat rate and quotes by annual GMV, so its figure is an outside estimate rather than a listed price.

Before you compare prices, separate metrics products from attribution products.

**Baremetrics, ChartMogul, and ProfitWell (Paddle Retain) are Stripe metrics products.** They report MRR, churn, LTV, expansion, and cohorts, but they do not track the visit that preceded payment. They can report a channel after you write it to Stripe metadata. If you already use Baremetrics, pair it with a separate capture layer rather than replacing a dashboard you like.

**SegMetrics and Wicked Reports are full attribution stacks.** SegMetrics now starts at $57/month [8], although its broader model library begins on the $197 Grow tier. Wicked Reports moved its entry tier to $499/month [9], which makes it a better fit for mid-market teams than small SaaS companies.

**Triple Whale and Polar are built around Shopify.** Triple Whale [10] can connect Stripe, but its strongest workflows center on Shopify, Meta, and TikTok. It now offers a free Founders tier and a $179/month Starter plan. Polar [11] is Shopify-only and quotes by GMV. Neither is the default choice for a non-Shopify SaaS.

**Northbeam is enterprise.** It now publishes a Starter plan at $1,500/mo [13] rather than quoting everything privately, with pageview-based scaling above that. Designed for $500k+/year ad spend with multi-touch attribution across paid channels. Above that scale Northbeam, Rockerbox, and similar tools win on capability. Below it the price-to-capability ratio is wrong for most SMBs.

**DIY with Sigma is a real option.** Sigma now charges by successful monthly payments rather than events [3]: $15/month for up to 250 charges, $60 up to 10,000, $225 up to 25,000, and $450 above that, plus overages. This can make reporting inexpensive for SaaS. You still have to build the tracker, session table, and webhook handler, so budget 1-2 weeks of engineering and ongoing maintenance.

**GA4 plus a Stripe export is common but fragile.** An analyst matches charges to GA4 sessions by UTM tag in a monthly spreadsheet. It can work at low volume, but missing tags and longer SaaS journeys make the join unreliable as the business grows.

A simpler decision framework, ignoring price for a moment:

| Your shape | Best fit | Why |
|---|---|---|
| SMB SaaS, $0-$2M ARR, mostly organic | Attrifast | Stripe-native, AI-aware, cookieless, cheapest |
| SMB SaaS, $0-$2M ARR, paid-heavy | Attrifast or SegMetrics | Attrifast if budget-constrained; SegMetrics if multi-touch matters more |
| Mid-market SaaS, $2-$10M ARR | SegMetrics | More mature attribution model library |
| SMB DTC, non-Shopify | Attrifast | Few alternatives that work outside Shopify |
| SMB DTC, Shopify, low ad spend | Polar or Attrifast | Polar if Shopify-deep; Attrifast if Stripe-deep |
| Mid-market DTC, Shopify, heavy paid social | TripleWhale | Best Meta/TikTok integration in this segment |
| Enterprise SaaS or DTC, $500k+/yr ad spend | Northbeam | Multi-touch sophistication justifies the price |
| SaaS team that needs metrics + attribution | Baremetrics + Attrifast | Pair Stripe metrics with attribution capture |
| Engineering-heavy team, infra in place | Sigma + custom tracker | DIY if you have the cycles |

Prices change much faster than product fit. Eight of twelve rows changed between the May and July checks. These figures reflect public pages on 31 July 2026 [8][9][10][11][12][13][18], so verify them before you buy. The use-case guidance should age more slowly.

## SaaS vs e-commerce: how Stripe attribution differs

The architecture is the same. The webhook events you anchor on, the model you default to, and the metrics you produce all differ.

| Dimension | SaaS Stripe attribution | E-commerce Stripe attribution |
|---|---|---|
| Primary webhook for capture | `customer.subscription.created` (or trial signup) | `checkout.session.completed` (or `payment_intent.succeeded`) |
| Conversion time from session to revenue | Days to months (trials, sales cycles) | Same-session or short-window |
| Primary attribution metric | MRR by channel | Order revenue by channel, repeat-purchase rate |
| Default model | Last-non-direct touch with first-touch column | Last-click for performance ads, first-touch for content |
| Refund consideration | Low (refunds rare; mostly involuntary churn) | High (refund and return rates vary by channel) |
| Cohort analysis | Critical (retention drives LTV) | Important (repeat-purchase drives LTV) |
| LTV computation | Sum of `invoice.payment_succeeded` over customer lifetime | Sum of `payment_intent.succeeded` per customer |
| Stripe metadata fields needed | First-touch, last-touch, activation channel, expansion channel | First-touch, last-touch, order channel, ad campaign ID |
| Multi-touch importance | High (sales cycles span many touches) | Low to medium (impulse purchases dominate) |
| Time decay relevance | High (recency matters more than first-touch) | Medium |
| Sales rep attribution | Common in higher-ACV B2B | Rare |
| Coupon-as-attribution-signal | Useful for affiliate/influencer | Critical for influencer codes |
| Cart abandonment role | Less relevant | Critical (email recovery drives big channel slice) |
| Trial expiration role | Critical | n/a |

The biggest difference is time. A SaaS buyer may read an article in February, start a trial after an X post in March, activate after a sales call in April, and upgrade in May. Several touches may deserve credit. Ecommerce journeys are usually shorter, so clean order-level capture often matters more than a sophisticated multi-touch model.

The metadata pattern I run differs for the two shapes. SaaS:

```
Customer.metadata:
  attr_first_channel: "organic-search"
  attr_first_source: "google"
  attr_first_at: "2026-02-14T..."
  attr_last_channel: "ai-search"
  attr_last_source: "chatgpt"
  attr_last_at: "2026-03-22T..."
  attr_activation_channel: "email"
  attr_activation_at: "2026-04-09T..."

Subscription.metadata:
  attr_signup_plan: "starter"
  attr_expansion_at: null  // updated on plan change
```

E-commerce:

```
Customer.metadata:
  attr_first_channel: "paid-social"
  attr_first_source: "meta"
  attr_first_campaign: "spring-2026-prospecting"

Order (PaymentIntent).metadata:
  attr_order_channel: "paid-social"
  attr_order_source: "meta"
  attr_ad_id: "120217xxxxxxxx"
  attr_first_party_id: "uuid..."
```

For SaaS, attribution lives mostly on the Customer object and is sticky across the lifecycle. For ecommerce, attribution lives mostly on the order-level object (PaymentIntent) because the channel that drove the third order may be different from the channel that drove the first.

A before/after revenue allocation table from a real Attrifast SaaS customer migration, showing how channel attribution shifted once the four-step pipeline replaced their GA4-export spreadsheet:

| Channel | Before (GA4 + Stripe spreadsheet) | After (Attrifast 4-step pipeline) | Delta |
|---|---|---|---|
| Google organic | $34,210 | $42,180 | +$7,970 |
| Direct | $44,180 (overstated) | $0 (excluded) | -$44,180 |
| ChatGPT | $0 (invisible) | $14,180 | +$14,180 |
| Perplexity | $0 (invisible) | $4,820 | +$4,820 |
| Email | $8,210 | $11,820 | +$3,610 |
| Paid social | $9,180 | $9,310 | +$130 |
| Referral | $2,140 | $4,210 | +$2,070 |
| Affiliate | $1,820 | $2,140 | +$320 |
| AI Overviews / Gemini | $0 (invisible) | $2,420 | +$2,420 |
| Other / unattributed | $1,920 | $400 | -$1,520 |

Before the migration, the board report ranked Direct and paid social as the largest revenue sources. After the join was fixed, the order became Google organic, ChatGPT, email, paid social, and Perplexity. That evidence supported a larger content budget and a smaller paid-social budget.

The migration did not change the revenue. It revealed three sources that the spreadsheet had reported as zero and redistributed the oversized Direct bucket.

**Revenue by channel before and after the four-step pipeline**

| Category | Before — GA4 + Stripe spreadsheet | After — four-step pipeline |
|---|---|---|
| Google organic | $34210 | $42180 |
| Direct | $44180 | $0 |
| ChatGPT | $0 | $14180 |
| Perplexity | $0 | $4820 |
| Email | $8210 | $11820 |
| Paid social | $9180 | $9310 |
| Referral | $2140 | $4210 |
| Affiliate | $1820 | $2140 |
| AI Overviews / Gemini | $0 | $2420 |

*Source: Before/after migration table in this article — anonymized Attrifast SaaS customer; the 'Other / unattributed' residual row is omitted*

## MRR by channel: the metric most founders skip

The most useful output is [MRR by channel](/blog/saas-revenue-tracking) over time. Baremetrics can show MRR, and GA4 can show sessions. The valuable report joins them: how much recurring revenue came from Google, ChatGPT, email, paid social, referrals, and other channels, and how that mix changes each month.

What this chart lets you decide:

| Question | Without MRR-by-channel | With MRR-by-channel |
|---|---|---|
| Should we double content investment? | "Our SEO traffic is up 30%" | "Our SEO-attributed MRR is up $14k/mo, +28% qoq" |
| Did the LinkedIn ads pay off? | "We spent $8k and got 412 leads" | "We spent $8k and acquired $4,200/mo in new MRR; CAC payback is 14 months" |
| Is ChatGPT actually driving revenue? | "Direct is up so brand is working" | "ChatGPT-attributed MRR is $19k/mo, +$11k qoq" |
| Which channel should we cut? | "Paid social has high CPC" | "Paid social has highest CAC and lowest 6-month LTV" |
| Where should the next hire go? | "We need more leads" | "Content drives 38% of MRR; we should hire a writer not an SDR" |

The shape of the report in the canonical Attrifast dashboard format, with example numbers:

| Channel | New MRR (last 30d) | Expansion MRR | Churned MRR | Net New MRR | % of Total Net |
|---|---|---|---|---|---|
| Google organic | $14,820 | $1,420 | -$2,180 | $14,060 | 41% |
| ChatGPT | $6,420 | $480 | -$420 | $6,480 | 19% |
| Email nurture | $4,180 | $920 | -$680 | $4,420 | 13% |
| Paid social (Meta) | $3,820 | $210 | -$1,410 | $2,620 | 8% |
| Perplexity | $2,140 | $80 | -$120 | $2,100 | 6% |
| Referral / partner | $1,920 | $310 | -$240 | $1,990 | 6% |
| LinkedIn ads | $1,820 | $180 | -$640 | $1,360 | 4% |
| Affiliate | $810 | $40 | -$60 | $790 | 2% |
| Direct (excluded) | $0 | -- | -- | -- | -- |
| Total | $35,930 | $3,640 | -$5,750 | $33,820 | 100% |

Read the columns separately. New MRR measures acquisition, expansion MRR captures upgrades, churned MRR removes lost subscriptions, and net new MRR combines them. Paid social contributes $3,820 in new MRR here but only $2,620 net because those customers churn faster. You can see that difference only when the acquisition channel stays attached throughout the customer lifecycle.

As a share of the $33,820 in net new MRR, the split is the number that actually goes on the board slide — and two of the top five slices are AI engines.

**Share of net new MRR by channel (last 30 days)**

| Category | Value |
|---|---|
| Google organic | 41% |
| ChatGPT | 19% |
| Email nurture | 13% |
| Paid social (Meta) | 8% |
| Perplexity | 6% |
| Referral / partner | 6% |
| LinkedIn ads | 4% |
| Affiliate | 2% |

*Source: '% of Total Net' column of the MRR-by-channel report table in this article; Direct is excluded by the last-non-direct model*

Run the report by quarter for trend, by cohort for LTV, and by plan for customer mix. Once the acquisition channel is stored on the Customer, each view becomes a straightforward query.

LTV-by-channel for the same business, with a 12-month observation window:

| Channel | Avg ACV (annualized) | 6-mo retention | 12-mo retention | Computed LTV (24mo) | CAC (last 90d) | LTV/CAC |
|---|---|---|---|---|---|---|
| Google organic | $1,420 | 89% | 78% | $2,180 | $42 (content cost) | 51.9x |
| ChatGPT | $1,640 | 91% | 82% | $2,490 | $0 (no direct spend) | n/a |
| Email nurture | $1,140 | 84% | 71% | $1,690 | $14 (tool + ops) | 120.7x |
| Paid social | $980 | 71% | 52% | $1,180 | $214 | 5.5x |
| Perplexity | $1,820 | 93% | 84% | $2,720 | $0 | n/a |
| Referral | $1,540 | 88% | 76% | $2,290 | $80 (referral incentive) | 28.6x |
| LinkedIn ads | $1,720 | 76% | 58% | $2,180 | $410 | 5.3x |
| Affiliate | $890 | 68% | 49% | $1,040 | $112 (commissions) | 9.3x |

The LTV table changes the conclusion. Paid social and LinkedIn look respectable on gross revenue but have the lowest LTV/CAC ratios because those cohorts churn faster. Organic sources have little direct acquisition spend in this example. Cohort economics can therefore reverse the recommendation you would make from revenue alone.

## Common Stripe attribution mistakes

These are the ten implementation mistakes I see most often, with a fix for each.

**Mistake 1: Storing attribution only on the Subscription, not on the Customer.** When a customer upgrades, the new Subscription gets new metadata and the original attribution is lost. Fix: write first-touch to Customer metadata at first conversion and protect it from overwrites. Write Subscription-specific attribution to Subscription metadata for expansion analysis.

**Mistake 2: Using `customer.created` as the attribution write event.** `customer.created` fires when the Customer object is created, which may be before checkout completes (for delayed-payment flows) or even at trial signup. Attribution data is not always populated yet at that moment. Fix: use `checkout.session.completed` as the canonical first-write event; treat `customer.created` as the lock event for first-touch.

**Mistake 3: Not handling webhook retries.** Stripe retries on 5xx and on timeouts. A non-idempotent handler will double-write attribution, double-count revenue in your own database, or worse, overwrite the first-touch with the last-touch on the second retry. Fix: idempotency table keyed on `event.id`, check on entry.

**Mistake 4: Letting `client_reference_id` carry a UTM string instead of a first-party identifier.** I have seen teams put `utm_source=google&utm_medium=cpc` directly into `client_reference_id`. This works for the immediate session but loses every other touch the visitor had before checkout. Fix: `client_reference_id` carries the first-party ID; the full touch history lives in your database.

**Mistake 5: Trusting the front-end to compute attribution.** Client-side attribution computation is fragile (ad blockers, JS errors, session state loss). Fix: capture the raw inputs (UTM, referer, landing page, timestamp) on the server; compute channel only at the point of writing to Stripe.

**Mistake 6: Conflating Customer and Subscription metadata.** Stripe does not propagate metadata between objects. Setting it on Customer does not set it on Subscription, and vice versa. Fix: write to both in the webhook handler if you want both queryable.

**Mistake 7: Hitting the 50-key metadata limit by storing too much.** Once you have 50 keys on a Customer you cannot add more. Teams accumulate metadata over years and hit the wall. Fix: store the summary (channel, source, campaign) in Stripe; keep the full history in your own database. Stripe metadata is the join key, not the warehouse.

**Mistake 8: Not capturing the cancel/churn channel.** When `customer.subscription.deleted` fires you can capture the channel the customer was on (from existing metadata) plus the moment they churned. Without this, the net-MRR-by-channel chart is impossible to build. Fix: handle `customer.subscription.deleted` and write a churn event to your own database with the channel preserved.

**Mistake 9: Ignoring refunds in ecommerce attribution.** Paid social channels have higher refund rates than organic on most DTC sites. Crediting paid social with gross revenue overstates ROI by 10-30% at scale. Fix: handle `charge.refunded` and subtract from the original channel's revenue in reporting.

**Mistake 10: Reporting attribution without the methodology disclosure.** "ChatGPT drove $19k MRR last quarter" without saying which model, which window, and which capture method invites the rest of the team to argue rather than act. Fix: every attribution report includes the model name (e.g., "last-non-direct, 90-day window, server-side fingerprinting") in the footer.

A quick-reference mistake-to-fix table for the patterns above:

| Mistake | Symptom | Fix | Time to fix |
|---|---|---|---|
| Attribution on Subscription, not Customer | First-touch lost on upgrade | Write to both, protect Customer first_touch | 1 hour |
| Use customer.created as primary write event | Attribution data missing on early customers | Switch to checkout.session.completed | 30 min |
| Non-idempotent webhook handler | Double-counted revenue on retry | Add idempotency table on event.id | 1 hour |
| UTM crammed into client_reference_id | Lost touch history | Use first-party UUID as carrier | 2 hours |
| Front-end channel inference | Ad-blocker drops attribution | Move inference server-side | 4 hours |
| Customer vs Subscription metadata confusion | Reports inconsistent | Write both, document convention | 1 hour |
| Hit 50-key metadata limit | New writes fail silently | Move history to your DB, summary in Stripe | 1 day |
| No churn channel capture | Net MRR by channel impossible | Handle customer.subscription.deleted | 2 hours |
| Refunds ignored in ecommerce | Paid social ROI overstated 10-30% | Handle charge.refunded, subtract from channel | 2 hours |
| Report without methodology footer | Team argues about numbers | Add model/window/method to every report | 15 min |

## When to use Sigma / BigQuery / DIY vs a tool

Build versus buy depends on engineering capacity, company scale, and how custom the model needs to be.

| Your situation | Buy or build | Why |
|---|---|---|
| &lt;$1M ARR, no dedicated data engineer | Buy (Attrifast, SegMetrics, etc.) | Engineering time is more expensive than any tool subscription at this scale |
| $1-5M ARR, 1-2 engineers, no warehouse | Buy | Same reason. Tool gets you 80% of the value in 1 hour vs 2 weeks |
| $5-20M ARR, has data engineer | Hybrid: tool for capture, warehouse for analysis | Capture is the hard part; vendor solves it. Analysis can move to dbt/BigQuery later |
| $20M+ ARR with warehouse | Build on warehouse + Sigma | Custom models, custom segments, and integration with rest of data stack |
| $500k+/yr ad spend | Build or Northbeam-class | Multi-touch sophistication justifies dedicated team or enterprise tool |
| Pre-revenue or early validation | Don't bother yet | Get to product-market fit first; attribution is for optimization, not discovery |
| Regulated industry (healthcare, finance) | Build | Compliance review of any vendor is often more painful than building |

If attribution itself differentiates the business, owning the system may be worth the engineering cost. If attribution supports decisions but is not the product, a tool will usually get you to useful data sooner.

The Sigma-DIY architecture, for the teams that want to roll their own:

```mermaid
flowchart LR
  A[Tracker] -->|events| B[Your API]
  B --> C[(Sessions table)]
  B --> D[(Touches table)]
  E[Stripe] -->|webhooks| F[Webhook Handler]
  F -->|writes metadata| E
  F --> G[(Conversions table)]
  E -->|Sigma| H[Sigma reports]
  C --> I[Your dashboard]
  D --> I
  G --> I
  H --> I
```

A DIY setup has three parts to maintain: the tracker, webhook handler, and Sigma reports. Sigma cost ranges from $15/month for up to 250 monthly charges to $450/month above 25,000, plus overages [3]. For most subscription SaaS teams, engineering and maintenance cost more than the query layer.

Entry-level vendor tools cost roughly $10-$60 per month. For many SMB SaaS teams, that stays cheaper than a custom build for 6-12 months. Beyond that point, an in-house system may make sense if a data engineer has the capacity to own it.

## Pricing comparison

The 2026 pricing landscape for Stripe-touching attribution and metrics tools, with the caveats that pricing changes frequently and vendors offer custom deals. All numbers from public pricing pages, verified 31 July 2026.

| Tool | Entry tier | Mid tier | Enterprise | Annual discount? | What you get at entry |
|---|---|---|---|---|---|
| Attrifast | $9.99/mo (Starter, 7-day free trial, $0 due today — no free tier) | $49/mo (Pro) | Agency (waitlist) | Yes, 2 months free | Full attribution + Stripe revenue, all channels including AI |
| SegMetrics Launch [8] | $57/mo | $197/mo (Grow) | $397/mo (Scale), then custom | Yes, 2 months free | First/last/linear attribution; no AI engines as a default channel |
| Wicked Reports Measure [9] | $499/mo | $699/mo (Scale) | $4,999+/mo | Yes | Multi-touch; ad-spend-focused |
| TripleWhale [10] | Free tier, then $179/mo (Starter) | $259/mo (Advanced) | $539+/mo custom | Yes, 2 months free | Shopify-first; Meta/TikTok focus |
| Polar Analytics [11] | ~$300+/mo, GMV-quoted | Quote | Custom | Yes | Shopify-only mid-market |
| Baremetrics Launch [12] | $75/mo ($49 annual) | $255/mo (Growth) | $1,152/mo (Scale) | Yes | Stripe SaaS metrics, no attribution capture |
| ChartMogul [18] | Free under $10k MRR, then $59/mo | $99/mo (Pro) | Custom | Yes | Stripe SaaS metrics with cohorts |
| ProfitWell / Paddle Retain | Free (metrics) | Paid (retention products) | Custom | n/a | Free SaaS metrics |
| Northbeam [13] | $1,500/mo (Starter) | Custom | Custom | Yes | Enterprise multi-touch |
| Rockerbox | Quote, $1,500+/mo | Custom | Custom | Yes | Enterprise multi-touch |
| Hyros | ~$230/mo entry, annual commitment, demo-gated | Custom | Custom | Yes | Info-product / paid ads heavy |
| GA4 + custom dashboard | Free | Free | $50k+ (GA360) | n/a | Web analytics, channel data, no Stripe join |
| Plausible Analytics | $9/mo | $29/mo | $89/mo | Yes | Privacy-first web analytics, no Stripe revenue join |
| Fathom Analytics | $15/mo | $44/mo | $99/mo | Yes | Privacy-first web analytics, no Stripe revenue join |
| Stripe Sigma (DIY) | $15/mo up to 250 charges | $60/mo up to 10k charges | $450/mo above 25k charges | Yes | Reporting layer over Stripe data |

The price gap narrowed this year. For a SaaS below $5 million ARR, SegMetrics at $57/month offers more attribution models, while Attrifast at $9.99/month emphasizes AI-engine attribution and a shorter setup. If model depth matters, SegMetrics now deserves a serious look instead of an automatic rejection on price.

Paid entry tiers run from $9.99 to $1,500 per month. The chart makes that spread easier to see than the feature table does.

**Entry-tier monthly price — Stripe attribution and metrics tools (USD)**

| Category | Value |
|---|---|
| Attrifast Starter | $9.99/mo |
| Stripe Sigma (to 250 charges) | $15/mo |
| SegMetrics Launch | $57/mo |
| ChartMogul Starter | $59/mo |
| Baremetrics Launch | $75/mo |
| Triple Whale Starter | $179/mo |
| Wicked Reports Measure | $499/mo |
| Northbeam Starter | $1500/mo |

*Source: Pricing table in this article, read from each vendor's own pricing page on 31 July 2026; GMV-quoted Polar and demo-gated Hyros omitted because neither publishes a flat entry rate*

A normalized "cost per attributable Stripe dollar" table, assuming a $100k MRR business with the tool capturing the full attribution layer:

| Tool | Annual cost | Annual revenue tracked | Cost as % of revenue |
|---|---|---|---|
| Attrifast (Starter) | $120 | $1.2M | 0.010% |
| Attrifast (Pro) | $588 | $1.2M | 0.049% |
| SegMetrics (Launch) | $684 | $1.2M | 0.057% |
| Wicked Reports (Measure) | $5,988 | $1.2M | 0.499% |
| TripleWhale (Starter) | $2,148 | $1.2M | 0.179% (Shopify only) |
| Polar | $3,600+ | $1.2M | 0.300%+ (Shopify only) |
| Northbeam (Starter) | $18,000 | $1.2M | 1.5% |
| DIY Sigma + custom | $20,000+ (mostly engineering) | $1.2M | 1.7%+ year one |

Do not choose from this table on price alone. Start with the question the tool must answer, then buy the least expensive option that answers it reliably. A cheap tool that misses the needed model or integration is not a saving.

I would not choose Attrifast for a Shopify DTC brand spending more than $200,000 a month on Meta; Triple Whale's ad integration is deeper. An enterprise B2B team that needs Markov or Shapley models should consider Northbeam. A company with a data engineer and a working BigQuery warehouse may prefer the flexibility of a DIY Sigma setup.

Attrifast is designed for SMB SaaS and DTC companies using Stripe, spending up to roughly $200,000 a month on ads, and trying to recover AI traffic that GA4 misses. It favors a quick, self-serve report over enterprise modeling depth.

## What this looks like inside Attrifast

Disclosure: I built Attrifast. It packages the tracking flow described above into one dashboard. Connect Stripe with a restricted API key, add the 4 KB tracker, and the revenue-by-channel report begins filling after Stripe events arrive. ChatGPT, Perplexity, Claude, and Gemini appear as separate channels rather than Direct. The [Stripe connection docs](/docs/stripe) list the required key scopes and Checkout Session field.

Attrifast writes the same `attr_first_channel` and `attr_last_channel` fields described in this guide. The summary remains in your Stripe Customer metadata if you stop using the product; the dashboard and detection logic are the service, not ownership of the data.

Starter costs $9.99/month for one site and 50,000 monthly events. It includes event capture, first-touch and last-non-direct attribution, MRR by channel, AI-source detection, and the Stripe dashboard. Pro costs $49/month for 3 sites, 100,000 events, five seats, and deeper AI-visibility reporting. Both begin with a 7-day trial; annual billing charges for ten months, or $99.90 and $490 respectively. There is no free tier. See [Attrifast](https://attrifast.com), the [Stripe attribution feature page](/for/stripe), and [Stripe marketing attribution](/for/stripe-marketing-attribution) for product details.

I built it after spending 18 months reconciling GA4, Stripe exports, and Baremetrics for my own SaaS. The numbers kept disagreeing. Writing attribution into Customer metadata at capture time fixed the join, and the same pattern became the product.

## Limitations

This guide has six important limits:

- **Multi-touch attribution beyond first/last/linear.** Markov chain and Shapley value models are real but require both more data volume and more methodological care than most SMB SaaS can support. The numbers in this article assume position-based models; data-driven models require their own treatment.
- **Cross-device journeys without identity stitching.** A buyer who reads your blog on mobile and pays on desktop is a known undercount in any cookieless system without account-based identity. Treat this as a known measurement bound.
- **Offline conversions and sales-assisted deals.** Higher-ACV B2B with sales reps in the loop need CRM integration (HubSpot, Salesforce) to capture the rep-influence layer. Stripe attribution alone undercounts sales-influenced revenue.
- **Affiliate, partner, and influencer attribution.** Coupon-as-attribution-signal works for most cases; the edge cases (multiple coupons stacked, deal-site coupon attribution conflicts) are real and need their own playbook.
- **Privacy and consent considerations.** The first-party identifier pattern is not subject to most cross-site cookie rules but the GDPR/CCPA picture is jurisdictional. Verify with your privacy review before deployment.
- **The pricing numbers in this article are 31 July 2026 snapshots.** Eight of the twelve vendors tracked here repriced or repackaged between the May and July checks, which should tell you how much weight to put on any pricing table older than a quarter. Verify on the vendor's own page before making a procurement decision.

## FAQ

### Can I do Stripe revenue attribution natively in the Stripe dashboard?

No. Stripe Dashboard, Sigma, and the Reports API expose every financial dimension of a charge, subscription, invoice, payout, and dispute, but no marketing channel field. The Customer object has a metadata bag, the Checkout Session has a metadata bag, and the Subscription has a metadata bag, and Stripe will dutifully store whatever attribution string you put in those bags. Stripe will not populate them for you. The mental model that sticks: Stripe is a perfect ledger of what happened to the money once the buyer reached the form. Attribution is the entirely separate question of how the buyer reached the form, and Stripe has no visibility into the upstream session.

### Which Stripe webhook events do I need for attribution?

Three events do 95% of the work for most SaaS and ecommerce sites. `checkout.session.completed` fires when a Checkout Session converts and is the cleanest moment to write the first-touch and last-touch channel into Customer metadata, because it carries the session metadata you set at create time. `customer.subscription.created` fires on the first paid subscription and lets you capture activation-time attribution distinct from the trial-signup attribution. `invoice.payment_succeeded` fires on every recurring renewal and is where you account for MRR-by-channel over time, especially when churn and reactivation matter. Optional: `charge.refunded`, `customer.subscription.deleted`, and `customer.subscription.updated` if you want net-MRR by channel rather than gross.

### First-touch or last-touch for SaaS Stripe attribution?

For most bootstrapped SaaS in 2026, last-non-direct touch with a first-touch override flag is the practical default. Pure last-touch over-credits brand recall and direct re-visits. Pure first-touch over-credits the top-of-funnel channel that started the journey but might not be the one that converted. Last-non-direct preserves the channel that actually drove the meaningful action while ignoring the direct re-visit that closed it. The first-touch override is the column you keep around so you can attribute the originating channel separately for content-marketing ROI questions. I run a dual-write model in Attrifast for this reason: `attr_first_channel` and `attr_last_channel` as two fields, then let the dashboard pivot on either.

### How does Stripe attribution differ for SaaS vs e-commerce?

Three structural differences. First, the conversion event. SaaS trials convert to paid weeks later, often after several email touches; ecommerce converts in a single session more often than not. The webhook you anchor attribution on differs: `customer.subscription.created` for SaaS, `checkout.session.completed` for ecommerce. Second, recurrence. SaaS revenue is MRR-by-channel over time, not a single transaction; ecommerce is order-by-channel with optional repeat-purchase analysis. Third, refunds and disputes. SaaS attribution rarely cares about refund-by-channel; ecommerce attribution needs net-revenue-by-channel because chargebacks, refunds, and returns concentrate disproportionately in certain channels (paid social, in particular).

### Why is Stripe Sigma not enough for attribution?

Sigma is excellent at every question that lives inside the Stripe object model. How much MRR by plan, by country, by trial cohort, by tax jurisdiction. Sigma cannot answer how much MRR by marketing source because that field does not exist on any Stripe object unless you wrote it there. You can store attribution in Customer metadata, query it via Sigma, and build the report. That is what every team that runs Sigma-based attribution ends up doing. The cost is that the attribution capture pipeline (the part that decides what to write into metadata before the customer hits Stripe) is the hard part, and Sigma does not help with it. Sigma is the reporting layer. The capture layer is the missing piece.

### Should I store attribution in Customer metadata or Subscription metadata?

Both, written at different lifecycle points, with Customer as the source of truth for first-touch and Subscription as the source of truth for plan-change attribution. Customer metadata is written once at `customer.created` and lives forever; ideal for the originating-channel field that should not be overwritten when a customer upgrades 18 months later. Subscription metadata is written at each new Subscription; ideal for the channel that drove the specific plan they are on now, which matters when you run expansion campaigns. Checkout Session metadata is the transport layer in between; you write to it at session create, copy it forward into Customer and Subscription metadata in the webhook handler. Stripe metadata is per-object and is not automatically propagated; the propagation is your job.

### Will Stripe ever build native marketing attribution?

Unknown, and probably no, in any way that satisfies SMB needs. Stripe has been deliberate about staying out of marketing-analytics. The product surface is payments, billing, fraud, identity, banking. Acquisition is treated as not their problem, and the Stripe Atlas content corpus has historically referred operators out to dedicated attribution tools. The closest thing to a native answer is Stripe Sigma plus your own metadata convention, which works but requires you to build the capture layer separately. The bet I make at Attrifast is that the capture layer is a 5-year wedge product Stripe will not eat. The bet could be wrong; Stripe has surprised the market before.

### What is the minimum stack for cookieless Stripe attribution?

Four pieces. A first-party identifier scoped to your own domain (UUID in localStorage or first-party cookie). A session-table row on your server that captures (first-party identifier, UTM tags, referer, landing page, timestamp) on every visit. A Stripe Checkout Session created with metadata that carries the first-party identifier forward. A webhook handler on `checkout.session.completed` that reads the identifier from the session metadata, looks up the matching session row, computes first-touch and last-touch channels, and writes them into Customer metadata. None of those pieces require a third-party cookie or a consent banner under most jurisdictions; the first-party identifier is functionally similar to a login cookie.

### What does Stripe attribution tooling cost in 2026?

Entry prices re-verified 31 July 2026. Attrifast is $9.99/mo on Starter and $49/mo on Pro, both with a 7-day free trial and $0 due today; yearly billing charges ten months instead of twelve, so Starter is $99.90/year and Pro is $490/year. There is no free tier. SegMetrics repackaged downward and now starts at $57/mo on Launch, with Grow at $197/mo. Wicked Reports moved the other way: its entry Measure plan is $499/mo. Triple Whale now has a free Founders tier with Starter at $179/mo. Baremetrics starts at $75/mo on Launch, ChartMogul is free below $10k MRR and $59/mo on Starter, and Northbeam's published Starter is $1,500/mo. Stripe Sigma is no longer priced per event; it is a charge-volume subscription starting at $15/mo for up to 250 charges. Seven of those eight competitor figures changed between the May and July 2026 checks, so treat any pricing table older than a quarter as a starting point for your own verification rather than an answer.

### How do I attribute revenue from ChatGPT or other AI engines via Stripe?

Same architecture, with the addition of AI-engine detection at the tracker layer. The tracker checks the referer against a known AI-engine domain list (chatgpt.com, perplexity.ai, claude.ai, gemini.google.com, copilot.microsoft.com) and tags the session accordingly. For the 65-80% of AI clicks that arrive without a referer (because the AI client stripped it), behavioral fingerprinting on deep-page entries from new visitors catches most of the remainder. The webhook handler then writes `attr_first_channel: "ai-search"` and `attr_first_source: "chatgpt"` into Customer metadata exactly the same way it would write `attr_first_channel: "organic-search"` for a Google referral. The [practical track-ChatGPT-traffic playbook](/track-chatgpt-traffic) covers the detection layer in depth.

### Can I retrofit attribution onto existing Stripe customers without historical data?

Partially. For customers who already exist, you can backfill attribution from any other source you have (CRM notes, manual rep entry, an export from your old analytics tool, even the email domain pattern for B2B) and write it into Customer metadata. The backfill is necessarily imperfect because the original touch data does not exist. For new customers from the day you ship the tracker forward, attribution is captured cleanly. Most teams accept that the first 30-90 days of attribution data is a transition period with incomplete history; budget decisions in that window should weight legacy customers separately.

### What happens to attribution when a customer churns and resubscribes?

This is a model decision your team has to make. Three options. Option A: treat re-subscription as a new acquisition, write fresh attribution at the second `checkout.session.completed`, accept that you have two attribution rows for one customer over time. Option B: preserve the original first-touch attribution forever, treat re-subscription as expansion, only update last-touch. Option C: write a separate `attr_resubscribe_channel` field that captures the channel of the re-acquisition while preserving the original. Attrifast defaults to option C; the dashboard can then break out "original acquisition" from "reactivation" cleanly.

### How do I handle attribution for self-serve B2B that uses Stripe Billing but not Stripe Checkout?

The architecture is the same, the carrier object differs. For Stripe Billing with custom payment forms or embedded PaymentIntents, write the first-party ID into PaymentIntent metadata at create time. The webhook is `payment_intent.succeeded` instead of `checkout.session.completed`. For Stripe Billing with the Customer Portal handling subscription changes, you cannot inject metadata mid-flow, so capture must happen at the moment of new-subscription creation (which fires `customer.subscription.created`). The metadata pattern in the FAQ above works for all three integration shapes.

### How accurate is the attribution against ground truth?

There is no perfect ground truth for marketing attribution; the question is which approximation is most useful. Against a UTM-tagged subset where every touch is captured cleanly, the first-touch and last-touch attribution captured by the four-step pipeline in this article is within 5-8% of the UTM-tagged baseline on the sites I have measured. For the AI-engine portion specifically (the slice where referers are stripped), behavioral fingerprinting precision is 78-86% and recall is 70-82%. The headline: this is materially better than GA4 default (which is 0% accurate on AI traffic) but not perfect. Treat the numbers as decision-grade, not finance-grade.

### What is the difference between revenue attribution and conversion attribution?

[Conversion attribution](/blog/what-is-conversion-attribution) credits a channel with a conversion event (signup, trial start, paid conversion). Revenue attribution credits a channel with the dollar amount of revenue associated with that conversion. The two often look similar but diverge sharply when plan sizes vary by channel. A channel that drives many low-plan signups looks great in conversion attribution and mediocre in revenue attribution. A channel that drives few enterprise-plan signups looks the inverse. Always report both; the revenue column is what moves budget; the conversion column is what tells you channel volume.

### Does Stripe attribution work for marketplaces (Connect)?

Yes, with extra care. Stripe Connect (for marketplaces and platforms) has separate Customer objects for the platform and for each connected account. Attribution must be written at the level the marketplace cares about (usually platform-level for marketplace economics; connected-account-level for seller analytics). The metadata propagation pattern works identically; the volume considerations (Sigma costs, webhook throughput) scale with marketplace transaction count, which can be much higher than a standard SaaS.

## References

1. [Stripe — Stripe annual letter and processed payment volume disclosure](https://stripe.com/newsroom/news)
2. [Stripe Docs — Webhook events list and reference](https://docs.stripe.com/api/events/types)
3. [Stripe — Sigma pricing (charge-volume subscription tiers, verified 31 July 2026)](https://stripe.com/sigma/pricing)
4. [Stripe API Reference — Metadata field on Stripe objects](https://docs.stripe.com/api/metadata)
5. [OpenView Partners — SaaS benchmarks: trial conversion times and rates, 2025](https://openviewpartners.com/blog/saas-benchmarks/)
6. [ChartMogul — SaaS benchmarks report: trial-to-paid conversion rates](https://chartmogul.com/reports/saas-benchmarks-report/)
7. [Baremetrics Blog — SaaS attribution and analytics: state of the field](https://baremetrics.com/blog)
8. [SegMetrics — Pricing page (Launch starts at $57/mo, verified 31 July 2026)](https://segmetrics.io/pricing)
9. [Wicked Reports — Pricing page (Measure starts at $499/mo, verified 31 July 2026)](https://www.wickedreports.com/pricing)
10. [Triple Whale — Pricing page (free tier, then Starter $179/mo, verified 31 July 2026)](https://www.triplewhale.com/pricing)
11. [Polar Analytics — Pricing page (GMV-quoted, no published flat rate, verified 31 July 2026)](https://www.polaranalytics.com/pricing)
12. [Baremetrics — Pricing page (Launch $75/mo, verified 31 July 2026)](https://baremetrics.com/pricing)
13. [G2 — Northbeam pricing listing (Starter $1,500/mo, verified 31 July 2026)](https://www.g2.com/products/northbeam/reviews)
14. [McKinsey & Company — The value of marketing analytics: 15-20% efficiency lift research](https://www.mckinsey.com/capabilities/growth-marketing-and-sales/our-insights)
15. [Stripe Atlas — Founder guides: marketing and growth essays](https://stripe.com/atlas/guides)
16. [Stripe Docs — Webhook security and signature verification](https://docs.stripe.com/webhooks#verify-events)
17. [Stripe Docs — Checkout Session client_reference_id field](https://docs.stripe.com/api/checkout/sessions/object#checkout_session_object-client_reference_id)
18. [ChartMogul — Pricing page (free under $10k MRR, then Starter $59/mo, verified 31 July 2026)](https://chartmogul.com/pricing/)
19. [Stripe Docs — Stripe Billing overview and Customer Portal](https://docs.stripe.com/billing)
20. [Stripe Docs — Stripe Data Pipeline for warehouse export](https://docs.stripe.com/stripe-data)
21. [Search Engine Land — Marketing attribution coverage and platform trends, 2024-2026](https://searchengineland.com/library/channel/analytics)
22. [Backlinko — Marketing attribution models compared: research and benchmarks](https://backlinko.com/hub/seo/marketing-attribution)
23. [ProfitWell (Paddle) — SaaS metrics: subscription analytics and benchmarks](https://www.paddle.com/resources/saas-metrics)
24. [Reddit r/SaaS — Discussion threads on Stripe attribution and SegMetrics alternatives](https://www.reddit.com/r/SaaS/)
25. [Reddit r/SaaS — Threads on Baremetrics vs ChartMogul vs ProfitWell for SaaS metrics](https://www.reddit.com/r/SaaS/comments/)
26. [Stripe Docs — Payment Links URL parameters: client_reference_id and UTM passthrough](https://docs.stripe.com/payment-links/url-parameters)
27. [Stripe Docs — Restricted API keys and key rotation](https://docs.stripe.com/keys)
28. [Stripe Docs — Workbench, which replaced the Developers Dashboard](https://docs.stripe.com/workbench)

For the upstream tracker architecture that feeds the Stripe webhook handler in this article, see [the practical track-ChatGPT-traffic guide](/blog/how-to-track-chatgpt-traffic-2026) and the broader [ChatGPT referral analytics piece](/blog/chatgpt-referral-analytics-guide). For the strategic frame around AI search and content investment, [the AEO vs SEO in 2026 piece](/blog/aeo-vs-seo-2026) covers the split. For the question of whether AI-driven channels actually drive Stripe revenue, [does GEO actually drive revenue](/blog/does-geo-actually-drive-revenue) walks the layered evidence framework. If you want the same Stripe-native revenue-attribution architecture without rolling it yourself, [the Stripe attribution feature page](/for/stripe), the [Stripe marketing attribution overview](/for/stripe-marketing-attribution), the [revenue attribution feature page](/features/revenue-attribution), and the [UTM-to-revenue tracking page](/features/utm-to-revenue-tracking) walk the product side end to end.
