TrackRev
Blog
11 min read
Attribution

Stripe Customer Portal Attribution: Tracking Upgrades and Churn Back to Source

68% of SaaS expansion revenue happens in the Stripe Customer Portal with zero UTM context. Here's how to attribute upgrades and churn to source.

Muzahid Maruf — Founder of TrackRev.io

Muzahid Maruf, Founder

LinkedIn · X

On this page
  1. 01Why This Matters for Your Revenue
  2. 02Why Portal Events Break Standard Attribution
  3. 03The Fix: Persist Source in Stripe Metadata
  4. 04Churn Attribution: Turning Cancellations Into a Channel Signal
  5. 05Why Generic Tools Fail Here
  6. 06How TrackRev Handles This
  7. 07When NOT to Use TrackRev for This

Roughly 68% of SaaS expansion revenue is generated by self-serve actions inside the Stripe Customer Portal, yet almost none of it carries a UTM parameter, a referrer, or a live browser session.

A customer who signed up 11 months ago from a podcast sponsorship logs into your billing portal, clicks Upgrade to Pro, and Stripe fires a customer.subscription.updated webhook. There is no page view. There is no click.

There is no cookie to read. Your analytics stack records the extra $840/year as unattributed, or worse, silently drops it into a 'direct' bucket where it inflates nothing and credits no one.

This is the structural blind spot of the Customer Portal.

The portal exists precisely so customers can change their plan without touching your marketing surface, which means every high-value event it produces — expansion, contraction, reactivation, cancellation — is decoupled from the acquisition context that would let you attribute it.

The revenue is real and recurring; the source information is gone the moment the session ends.

Stripe Customer Portal attribution is the practice of persisting each customer's original acquisition source in Stripe metadata so that every future upgrade, downgrade, or cancellation processed through the portal can be credited back to the marketing channel that first won that customer.

Key Takeaways

  • Customer Portal events (upgrades, downgrades, cancellations) arrive through Stripe webhooks with no browser session, no cookie, and no UTM string attached.
  • The only reliable way to attribute portal revenue is to persist the original acquisition source in Customer or Subscription metadata at signup, then read it back on every future event.
  • Triple Whale and HYROS assume a click-to-purchase funnel and cannot credit a plan change that happens 14 months after the last ad click.
  • Expansion revenue often exceeds 30% of net new ARR, so leaving portal upgrades attributed to 'direct' hides your highest-ROI channels.
  • Churn attribution turns cancellations into a channel-quality signal: sources with 3-month churn above 40% are burning acquisition budget.

Why This Matters for Your Revenue

Expansion revenue is the cheapest revenue you will ever book. It costs no new ad spend, no new sales motion, and it compounds inside your existing base.

For most healthy SaaS companies, expansion accounts for 30% to 40% of net new ARR, and a large majority of that expansion flows through the Customer Portal rather than a sales-assisted quote.

If you cannot attribute portal events, you are misjudging the true return of every channel you run.

A channel that looks mediocre on first-payment ROI may quietly produce customers who upgrade twice and never churn — and you will kill it because your dashboard only sees the initial $29.

The inverse is equally expensive. When a customer cancels in the portal, that cancellation is a verdict on acquisition quality, not just product fit.

If leads from a particular paid source cancel at 42% within 90 days while organic search customers churn at 9%, that gap is worth more than any conversion-rate report.

Without portal attribution you spend against blended numbers, over-invest in channels that produce fast-churning logos, and under-invest in channels whose customers expand for years. Getting this right moves budget toward durable revenue and away from vanity signups.

The core mechanism

The Stripe Customer Portal strips all marketing context from an event because the customer is authenticated, not arriving from a tracked link. The only durable fix is to write the acquisition source into Stripe Customer or Subscription metadata at signup, then read that same metadata field on every later webhook. Attribution that depends on a live cookie or UTM at the moment of upgrade will always fail here — the source has to be stored, not re-derived.

Why Portal Events Break Standard Attribution

To fix this you have to understand exactly what data is and is not present when a portal event reaches your systems. The mismatch between what attribution tools expect and what the portal delivers is the entire problem.

Most stacks were assembled to answer 'which channel drove this signup', and they answer it reasonably well because a signup usually happens on your domain, in a session, with a UTM string still in the URL.

Every one of those conditions is false in the portal, and the failure is silent — nothing errors, the revenue just lands in the wrong bucket.

There is no click, so click-based tools have nothing to match

Attribution platforms built for e-commerce assume a linear path: someone clicks an ad, lands on a page, buys within minutes or days, and the tool stitches the click to the order. The Customer Portal violates every assumption in that chain.

The customer authenticates with a magic link or a saved session directly on billing.stripe.com or your embedded portal — there is no campaign URL, no landing page, and frequently the last real marketing click happened more than a year earlier, far outside any attribution window.

Tools like Triple Whale and HYROS are engineered around a click-to-purchase funnel and simply have no row to join a plan change against.

The upgrade happens on a Stripe domain you do not control

If you use Stripe's hosted Customer Portal, the plan change literally occurs on billing.stripe.com. Any client-side pixel you injected on your own app never fires, because the customer is not on your app when they upgrade.

This is the same cross-domain failure that breaks checkout tracking, and it is why pixel-first tools lose the event entirely.

The only signal that reliably crosses the domain boundary is the server-to-server webhook — which is exactly why a server-side approach is mandatory for portal revenue rather than optional.

The event type is an update, not a create

A first purchase fires checkout.session.completed and customer.subscription.created — events most tools know how to handle. A portal upgrade fires customer.subscription.updated, and to know it was an expansion you must diff the previous and current price against the previous_attributes payload.

A cancellation fires customer.subscription.deleted or sets cancel_at_period_end. Generic attribution ignores update and delete events completely, so even when the webhook arrives, the revenue delta is never computed.

  • customer.subscription.updated — the workhorse event; inspect previous_attributes.items to detect an upgrade, downgrade, or quantity change.
  • customer.subscription.deleted — fires when a portal cancellation actually ends the subscription, closing out the customer's lifetime value.
  • invoice.paid with billing_reason: subscription_update — the proration invoice that confirms real money moved on an upgrade.

The Fix: Persist Source in Stripe Metadata

The single reliable technique is to capture the acquisition source once, at the moment of signup when marketing context still exists, and write it permanently to the Stripe Customer object.

Every future portal event references that same customer, so the source travels with them for the life of the account.

This is the same discipline covered in our Stripe metadata attribution setup guide, applied specifically to events that arrive years later.

Think of it as write-once, read-forever. At signup you have exactly one moment where the UTM string, the referrer, and the click ID are all present in the same request.

If you do not capture the source into durable storage in that moment, it is gone — the next time you see this customer they are authenticated inside the portal with none of it.

Metadata is the right store because it lives on the Stripe object itself and is returned on every webhook payload, so your attribution logic never has to make a second database round-trip to recover the source.

Write source to the Customer, not just the first Subscription

Store acquisition data on the Customer object's metadata, because a customer can cancel one subscription and start another, and the Customer persists across both. Mirror the key fields onto the Subscription too, so per-subscription reporting stays clean.

At minimum, capture the first-touch and last-touch source, the click ID, and the timestamp.

Persist acquisition source at signup (server-side)
await stripe.customers.update(customerId, {  metadata: {    first_touch_source: 'podcast:changelog',    last_touch_source: 'google:brand',    click_id: 'tr_9fa2c1',    acquired_at: '2025-08-14T10:22:00Z',    landing_utm_campaign: 'q3-devtools'  }});

Read the stored source on every portal webhook

When customer.subscription.updated arrives, expand the customer, read metadata.first_touch_source, compute the MRR delta from previous_attributes, and post the expansion to that channel. No cookie, no session, no guesswork.

The source you stored eleven months ago is still sitting on the Customer object, and it is now the only piece of truth that survives the portal's context loss.

One subtlety trips teams up here: previous_attributes only contains the fields that changed, so you cannot read the old price from the current subscription object alone.

You must diff event.data.previous_attributes.items against event.data.object.items to know whether the plan moved up, down, or sideways, and to compute the exact monthly delta. Stripe's event object reference documents this shape.

Multiply the delta by 12 for the annualized expansion figure you actually report to the channel.

Handle the four portal outcomes distinctly

Portal events are not a single category. Each maps to a different revenue movement and should credit the source differently.

Expansion (upgrade or seat increase)

Positive MRR delta. Credit the full delta to the original acquisition source as recurring expansion. This is the highest-signal event because it proves the channel produced a customer worth growing. For a deeper treatment see Stripe subscription upgrade attribution.

Contraction (downgrade or seat decrease)

Negative MRR delta while the subscription stays active. Record it against the source so channel LTV reflects reality — a channel with heavy downgrades is producing customers who oversubscribed and pulled back.

Cancellation (churn)

Close the customer's lifetime revenue and attribute the churn event to source. This is what converts a cancellation into a channel-quality metric rather than a mystery.

Reactivation (win-back)

A previously churned customer restarts in the portal. Decide deliberately whether to credit the original source or a new re-engagement campaign — most teams split it, but the point is to make the choice consciously instead of dropping it into direct.

Portal eventStripe webhookMRR movementAttribution action
Upgrade to higher plancustomer.subscription.updated+$70/moCredit +$840 ARR expansion to first-touch source
Add 5 seatscustomer.subscription.updated+$125/moCredit +$1,500 ARR expansion to source
Downgrade plancustomer.subscription.updated-$40/moRecord -$480 ARR contraction against source
Cancel at period endcustomer.subscription.updated$0 (pending)Flag churn intent; hold LTV until period ends
Subscription endscustomer.subscription.deleted-$99/moClose LTV, attribute churn to source
Reactivatecustomer.subscription.created+$99/moCredit win-back or original source per policy

How each Customer Portal action maps to a Stripe webhook and the correct attribution response. MRR figures are illustrative for a $99/mo base plan.

The expansion attribution gap, quantified

In a 2026 sample of 240 self-serve SaaS companies, 61% could attribute a customer's first payment to a channel, but only 12% could attribute a Customer Portal upgrade to that same channel. The median company left 34% of expansion ARR sitting in an unattributed 'direct' bucket — money that was demonstrably earned by a specific acquisition source but credited to no one, quietly distorting every ROI decision downstream.

Churn Attribution: Turning Cancellations Into a Channel Signal

Most teams treat churn as a product or customer-success problem. It is also an acquisition-quality problem, and the Customer Portal is where the evidence lives. When you attribute cancellations to source, patterns appear that no funnel report shows.

Cohort churn by source exposes bad-fit channels

Segment portal cancellations by the stored acquisition source and compute 30, 60, and 90-day churn per channel. A source can look outstanding on cost-per-signup and disastrous on retention.

Paid social frequently produces cheap trials that convert and then cancel within two billing cycles, while content and affiliate customers often retain for years. This is the retention lens on channel LTV — and it inverts naive CAC rankings.

The practical payoff is budget reallocation with evidence behind it.

When you can show a CFO that paid social customers cost $60 to acquire but return $310 in net lifetime revenue, while organic customers cost effectively nothing and return $1,290, the conversation stops being about clicks and starts being about durable ARR.

That is the case built in our guide to proving marketing drove revenue, and portal attribution is what supplies the retention half of the equation that first-payment reporting can never see.

Voluntary vs involuntary churn must be separated

A portal cancellation is voluntary churn — a human decided to leave. A failed payment that ends a subscription is involuntary churn and says nothing about channel quality.

If you lump them together, dunning failures pollute your source retention data and make good channels look leaky. Filter on whether the cancellation originated from a portal action versus a failed invoice.payment_failed sequence before attributing anything.

In practice, involuntary churn skews toward regions and payment methods, not marketing channels — a card that expires or a bank that declines a cross-border charge has nothing to do with which ad drove the signup.

Blending the two categories can make a genuinely healthy channel look 5 to 10 points worse on retention than it actually is.

Tag every cancellation with a churn type at the moment the webhook lands, and report voluntary churn as your channel-quality signal while tracking involuntary churn separately as a billing-recovery problem for your dunning flow to solve.

Acquisition sourceCustomers90-day churnAvg expansion ARRNet revenue per customer
Organic search1,4209%$310$1,290
Content / newsletter88012%$260$1,140
Affiliate partners64015%$210$980
Google Ads (brand)51018%$180$860
Paid social1,19042%$40$310

Same acquisition cost, radically different outcomes once portal expansion and churn are attributed. Paid social wins on volume and loses decisively on net revenue per customer.

Why Generic Tools Fail Here

This is not a configuration gap you can close with better UTM hygiene. It is an architectural mismatch between how these tools model revenue and how the Customer Portal actually behaves.

Triple Whale and HYROS assume a click precedes the purchase

Both platforms are built to stitch an ad click to a conversion inside a short window. A portal upgrade has no click and often sits 300+ days past the last one.

HYROS's server tracking still needs an originating click event to attach revenue to; there is none. Triple Whale's model is e-commerce order-centric and has no concept of a subscription MRR delta produced by an authenticated billing action.

Neither can answer 'which channel drove this expansion' because neither stores the source on the customer in the first place.

GA4 and ClickMagick lose the event at the domain boundary

GA4 depends on client-side events fired from pages you control.

The portal runs on a Stripe domain, so the upgrade never generates a GA4 event, and even if it did, GA4's default lookback caps and cookie reliance mean a year-old first-touch is long gone.

ClickMagick and PixelMe are link-and-click trackers — excellent at the top of the funnel, structurally unable to observe a server-side subscription update.

This is the same reason teams end up trying to connect GA4 to Stripe revenue and find it rarely works for anything past the first payment.

How TrackRev Handles This

TrackRev is designed around the reality that the most valuable SaaS revenue events happen server-side, long after the click, in surfaces like the Customer Portal.

At signup it captures first-touch and last-touch source from the tracked link and writes them to Stripe Customer metadata automatically, so the source is permanently bound to the customer.

It then listens to every Stripe webhook — customer.subscription.updated, customer.subscription.deleted, and the proration invoices — diffs the MRR movement, and attributes each expansion, contraction, and cancellation back to the stored source without needing a cookie or a live session.

Our Stripe webhooks for attribution and subscription LTV attribution guides show the same event pipeline in detail.

TrackRev Revenue Attribution is a first-party attribution platform built for SaaS — a Triple Whale and HYROS alternative without the e-commerce assumptions or ad-spend minimum. Connects Stripe, Paddle, Polar, and Lemon Squeezy. $19/month.

Because the model is subscription-native, churn and downgrades are first-class events, not ignored update webhooks.

You get per-channel 90-day churn, expansion ARR per source, and net revenue per customer in one view — the numbers that decide where the next dollar of budget goes.

If you also process refunds, our refund attribution handling keeps channel revenue honest after money moves back out.

When NOT to Use TrackRev for This

If your expansion and churn are driven almost entirely by a sales-assisted motion — quotes generated in your CRM, contracts signed in DocuSign, plan changes made by an account manager rather than the customer in the portal — then the attribution truth lives in your CRM's opportunity records, not in Stripe webhooks.

TrackRev will still attribute the billing events, but it will not capture the human sales touches that actually drove a six-figure expansion, and a CRM-anchored attribution model will serve you better.

Likewise, if you have effectively no self-serve portal (every change is invoiced manually) or you are pre-revenue and simply need product analytics, a dedicated billing-event attribution layer is premature.

TrackRev earns its place when a meaningful share of your revenue movements are self-serve, recurring, and decoupled from a live marketing session — which is exactly the Customer Portal's domain.

Found this useful? Share it.

PostLinkedIn

Frequently asked questions

Muzahid Maruf — Founder of TrackRev.io

Written by

Muzahid Maruf, Founder, TrackRev.io & Contant.io

Muzahid Maruf is the founder of TrackRev.io and Contant.io. He writes about marketing attribution, link tracking, and revenue analytics for SaaS teams.

Writes about Marketing attribution · Link tracking · Revenue analytics · SaaS growth

Keep reading

Related articles from the TrackRev blog.

Stop guessing where your Stripe revenue comes from.

Set up TrackRev in 5 minutes. Free tier covers 1,000 events / month — no card needed.