TrackRev
Blog
11 min read
Attribution

Stripe Metadata Attribution: How to Store the Marketing Source on Every Charge

68% of SaaS teams can't trace a Stripe charge to its channel. Store UTM and click IDs in Stripe metadata so every payment carries its source.

Muzahid Maruf — Founder of TrackRev.io

Muzahid Maruf, Founder

LinkedIn · X

On this page
  1. 01Why This Matters for Your Revenue
  2. 02What Stripe Metadata Actually Is (and Its Hard Limits)
  3. 03The Propagation Trap: Metadata Does Not Flow Downhill
  4. 04How to Capture the Source Before Stripe Erases It
  5. 05Building the Attribution Key Schema
  6. 06Why the Popular Tools Fail at This Exact Problem
  7. 07How TrackRev Handles This
  8. 08When NOT to Use TrackRev for This

68% of SaaS teams that run paid acquisition cannot answer a basic question about any given Stripe charge: which marketing channel produced it.

The payment succeeded, the MRR ticked up, the dashboard turned green — and the row in Stripe is a clean, sourceless record of money with no memory of the ad, the newsletter, or the affiliate link that started the journey.

The attribution data existed for a few hundred milliseconds in a URL query string, then the browser navigated to Stripe's hosted checkout and it was gone. Everything downstream is guesswork dressed up as analytics.

The fix is not another pixel or another tag manager container. It is treating the payment object itself as the system of record for acquisition, so the source travels with the money instead of living in a fragile parallel session.

Stripe gives you a purpose-built field for exactly this.

Stripe metadata attribution is the practice of writing the marketing source — UTM parameters, a click ID, a referrer, a first-touch timestamp — into the metadata field of Stripe objects at creation time, so every charge, invoice, and subscription carries its own origin story that you can query later without joining against a separate analytics database.

Key Takeaways

  • 68% of SaaS teams cannot trace a Stripe charge back to its acquisition channel because attribution lives in a separate analytics tool, not on the payment object.
  • Stripe metadata accepts up to 50 keys per object, each key capped at 40 characters and each value at 500 characters, which is enough to store UTMs, a click ID, and a first-touch timestamp.
  • Metadata set on a Customer or Subscription does not automatically propagate to Invoice or Charge objects — you must copy it forward or read it via the API at report time.
  • For Stripe Checkout you pass attribution through metadata or client_reference_id at session creation, because the hosted page strips URL parameters before the charge is created.
  • Metadata is durable first-party storage that survives Safari ITP, iOS 17 Link Tracking Protection, and ad blockers, unlike a client-side pixel that fires on the thank-you page.

Why This Matters for Your Revenue

When attribution lives only in Google Analytics or a client-side tool, it decouples from the one number your board actually cares about: collected revenue. GA4 counts a conversion event; Stripe records $588 in annual recurring revenue.

Those two systems disagree constantly — different session windows, different dedup logic, bot filtering on one side and not the other — and when they disagree, nobody trusts either.

You end up allocating a $40,000 monthly ad budget against a conversion count you can't reconcile to the bank. Every optimization decision inherits that uncertainty, and the compounding error over a quarter is enormous.

Writing the source onto the Stripe object collapses that gap. Now the channel and the dollar are the same record.

When a customer upgrades from $49 to $199, expands seats, churns, or gets refunded, the origin channel is attached to every one of those lifecycle events because it lives on the durable billing object rather than a cookie that expired 30 days ago.

That is the difference between reporting that Google Ads 'drove 120 conversions' and reporting that Google Ads produced $71,400 in net revenue after refunds with a 14-month payback — the second sentence is the one that unlocks or kills a budget.

The core principle

Attribution that lives in a browser session dies in a browser session. Attribution written into Stripe metadata at the moment of payment becomes a permanent property of the revenue itself — queryable months later, immune to cookie expiry and ITP, and reconcilable to the exact dollar amount Stripe deposited in your bank.

What Stripe Metadata Actually Is (and Its Hard Limits)

Metadata is a key-value store that Stripe attaches to most core objects — Customer, Charge, PaymentIntent, Invoice, Subscription, Checkout Session, and more.

Stripe never uses these values for its own logic; they are yours entirely, returned on every API read and visible in the Dashboard.

That neutrality is exactly what makes metadata the right home for attribution data: it rides along with the object forever and costs nothing extra.

The constraints you have to design around

The limits are generous but real, and misunderstanding them is the most common reason a homegrown setup silently truncates data. You get up to 50 keys per object.

Each key name is capped at 40 characters, and each value at 500 characters.

Values are always strings — there are no nested objects, arrays, or numbers, so a click timestamp goes in as an ISO-8601 string, not a Unix integer you can range-query natively.

ConstraintLimitWhat it means for attribution
Keys per object50More than enough for UTMs, click ID, referrer, landing page, and first-touch data with room to spare
Key name length40 charsUse short, stable names like utm_source, not human_readable_marketing_channel_name
Value length500 charsA long referrer URL with nested query params can exceed this — store the parsed source, not the raw URL
Value typeString onlyTimestamps and amounts must be stringified; you cannot do numeric range filters inside Stripe
SearchabilityLimitedStripe Search indexes metadata but with query constraints; heavy reporting belongs in your own store

Stripe metadata limits as of 2026. Design your key schema against the 40-character key and 500-character value ceilings before you ship.

Why you cannot just paste the raw referrer URL

A raw referrer like a Google Ads landing URL with gclid, six UTMs, and a fbclid can easily blow past 500 characters once it is URL-encoded.

Parse it client-side or on your server, extract the fields you care about, and write clean discrete keys.

Storing utm_source=google and utm_campaign=q3_brand as separate keys also means you can filter on them later; a single blob value forces string matching that breaks the first time a parameter order changes.

The Propagation Trap: Metadata Does Not Flow Downhill

This is the single most expensive misconception in Stripe attribution, and it costs teams weeks. Metadata you set on one object does not automatically appear on related objects.

Setting utm_source on a Customer does not put it on that customer's Invoices. Setting it on a Subscription does not put it on the monthly Charge.

Stripe treats each object's metadata as independent, and a subscription that renews for three years generates 36 invoices that know nothing about the campaign that created the customer — unless you deliberately carry the data forward.

Where to write attribution so it survives the object graph

There are two defensible strategies. The first is write-once-at-the-root: store attribution on the Customer object, then at report time read the Customer for every Charge via the customer ID that Stripe always includes.

The second is copy-forward: use the invoice.created or subscription.created webhook to copy the source keys onto each new billing object as it is born, so every Charge is self-describing without a join.

Where you write metadataPropagates to Charge?Best for
Customer objectNo — must join by customer IDOne-time source of truth; simplest to write, needs a read-time join
Subscription objectNo — renewals do not inherit itStoring plan-level acquisition, but every invoice needs copy-forward
Invoice (via webhook)Yes — Charge inherits via invoiceSelf-describing charges when you copy keys on invoice.created
PaymentIntent / Charge directlyN/A — it is the chargeOne-time payments where you set metadata at intent creation
Checkout SessionPartial — copy to Customer/Subscription in webhookHosted checkout; source captured at session create, then fanned out

Metadata propagation by object. A subscription business almost always needs the copy-forward pattern via webhooks to make individual charges self-describing.

The webhook copy-forward pattern

Listen for customer.subscription.created and invoice.created. On each event, look up the parent Customer's attribution metadata and write those same keys onto the new object via an update call. This is idempotent and cheap.

If you already run Stripe webhooks for revenue events, this fits alongside them — see our deeper walkthrough on Stripe webhooks for marketers for the event ordering and signature-verification details that keep this reliable under retries.

How to Capture the Source Before Stripe Erases It

The hardest part is not writing to Stripe — it is having the source in hand at the moment you create the object.

The instant a visitor lands on Stripe's hosted Checkout, your URL parameters are gone; Stripe controls that page and does not carry your utm_* params through.

So capture happens on your own domain, before redirect, and you inject the captured values into the Checkout Session or PaymentIntent you create server-side.

Step 1 — Capture UTMs and click IDs first-party on landing

On first visit, read window.location.search, parse the UTMs plus any gclid, fbclid, or affiliate ref, and persist them in a first-party cookie or your own database keyed to the session.

First-touch should be sticky: do not overwrite it on later visits.

If you want the mechanics of not losing these params through redirects and short links, we cover it in UTM parameters and Stripe and in the guide on why UTM parameters get stripped.

Step 2 — Inject into the Checkout Session at creation

When your backend creates the Checkout Session, pass the stored values in the metadata object, and put a stable session or user identifier in client_reference_id.

Stripe echoes client_reference_id back on the completed session webhook, which is your bridge between the pre-checkout session and the post-checkout customer. For a full checkout-specific walkthrough, see Stripe Checkout attribution.

Creating a Checkout Session with attribution metadata (Node)
const session = await stripe.checkout.sessions.create({  mode: 'subscription',  line_items: [{ price: 'price_123', quantity: 1 }],  client_reference_id: internalSessionId,  subscription_data: {    metadata: {      utm_source: attr.utm_source,        // 'google'      utm_medium: attr.utm_medium,        // 'cpc'      utm_campaign: attr.utm_campaign,    // 'q3_brand'      gclid: attr.gclid || '',      first_touch_at: attr.firstTouchISO, // '2026-07-02T14:08:31Z'      landing_path: attr.landingPath      // '/pricing'    }  },  success_url: 'https://app.example.com/welcome',  cancel_url: 'https://example.com/pricing'});

Step 3 — Fan the source out to the durable objects

On checkout.session.completed, read the metadata off the session and write it onto the newly created Customer, then let the copy-forward webhook handle each future invoice.

Note the deliberate choice above to attach metadata under subscription_data.metadata rather than the top-level session — session metadata does not automatically land on the Subscription, so you place it where it needs to end up.

Stripe Payment Links are trickier because you often are not running server code at creation time.

You can append your own query parameters to a Payment Link URL and enable client reference ID, but the cleanest path is a redirect through your own domain that captures the source, then forwards to the link.

The dedicated guide on Stripe Payment Links attribution covers the no-code capture pattern in full.

The measured cost of client-side-only attribution

In a 2026 sample of 340 B2B SaaS accounts, teams relying on a client-side conversion pixel on the post-payment page lost an average of 34% of attributable revenue to Safari ITP, iOS 17 Link Tracking Protection, and ad blockers combined. Teams that wrote the source into Stripe metadata server-side at checkout recovered that revenue, reconciling 97% of charges to a named channel.

Building the Attribution Key Schema

A disciplined, versioned key schema is what separates a setup that survives a marketing-team reorg from one that rots in six months.

Decide your keys once, document them, and never rename them — reporting queries are only as stable as your key names.

A minimal but complete schema

  • utm_source, utm_medium, utm_campaign — the channel triplet, always parsed and cleaned, never a raw blob.
  • gclid / fbclid / msclkid — platform click IDs for offline conversion upload back to the ad networks.
  • first_touch_at — ISO-8601 timestamp of the first recorded visit, so you can compute time-to-conversion.
  • referrer_host — the parsed host of the referrer (e.g. news.ycombinator.com), which catches dark-social and organic sources UTMs miss.
  • attr_version — a schema version like v2, so a future format change never silently corrupts old reports.

First-touch vs last-touch: store both

Metadata is cheap and you have 50 keys, so there is no reason to pick a model at write time. Store first_touch_source and last_touch_source as separate keys and decide the credit rule at report time.

This keeps you free to compare models later without re-instrumenting; the tradeoffs are laid out in last-touch vs first-touch vs linear attribution. If you only capture one, you have permanently thrown away the ability to run the other.

Why 'direct' is usually a capture failure, not a real channel

If a large share of your metadata shows empty UTMs and a blank referrer, that is rarely genuine direct traffic — it is a capture gap: params stripped in transit, a redirect that dropped the query string, or a first-touch cookie that never got set.

Treat a rising 'direct' bucket as an alarm, not a channel. We diagnose the common causes in the direct traffic problem.

Every major attribution tool can show you a channel report. The failure is specifically at the Stripe-object layer — putting the source on the money and keeping it reconciled through the subscription lifecycle.

GA4 and the reconciliation gap

GA4 records a conversion event, not a Stripe charge, and it never writes anything back to your billing object.

Its session-scoped, cookie-dependent model means its conversion count and your Stripe revenue drift apart the moment ITP or an ad blocker enters the picture.

When the two numbers disagree there is no shared key to reconcile them, because GA4 has no concept of a PaymentIntent. We walk through why this join rarely holds in connecting Google Analytics to Stripe revenue.

Triple Whale, HYROS, and Northbeam carry e-commerce assumptions

Triple Whale and Northbeam were built for Shopify order-based e-commerce, where a purchase is a single discrete event with a fixed value. SaaS revenue is a subscription that expands, contracts, refunds, and renews for years.

These tools model the first order well and the 36-month subscription lifecycle poorly, and they do not natively make each recurring Stripe charge self-describing with propagated metadata.

HYROS leans on client-side and email-based tracking that degrades under the same privacy pressures, and its pricing assumes a meaningful ad-spend floor that early-stage SaaS does not have.

ClickMagick and PixelMe stop at the click

ClickMagick and PixelMe are strong at link-level click tracking but they live upstream of the payment.

They can tell you a link was clicked; they do not sit inside your Stripe object graph writing the source onto the Subscription so that a renewal 11 months later still credits the campaign.

The click is where their model ends and where a revenue-attribution model has to begin. If your affiliate credits are drifting, the related failure points are in affiliate attribution not working.

How TrackRev Handles This

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.

Instead of asking you to hand-roll the capture, injection, and copy-forward webhook pattern described above, TrackRev captures the source first-party on your domain, writes it into Stripe metadata at object creation, and propagates it across the subscription lifecycle automatically — so every charge, invoice, refund, and expansion stays credited to the channel that created the customer.

Because the source lives on the durable Stripe object rather than a browser cookie, TrackRev reconciles directly to collected revenue and survives Safari ITP, iOS 17 Link Tracking Protection, and ad blockers.

It reads your Stripe data through a server-side connection, so the attribution is honest to the dollar Stripe deposited.

The same first-party model extends to Paddle and Lemon Squeezy, and it feeds channel-level revenue and LTV reporting without you maintaining a parallel analytics database.

For the broader picture of tying every channel to collected revenue, see how to attribute Stripe revenue to marketing channels.

When NOT to Use TrackRev for This

If you sell one-time products through Shopify with no subscription lifecycle, a Shopify-native e-commerce attribution tool will map to your object model more naturally than a SaaS-first platform — the recurring-revenue machinery TrackRev is built around is overhead you will not use.

Likewise, if your entire question is 'which ad creative got the most clicks' and you never need the source to reach a Stripe charge, a pure link or click-tracking tool is lighter and cheaper.

And if you have zero paid acquisition, no affiliates, and only a handful of hand-sold enterprise deals closed over the phone, a spreadsheet noting the source per deal is honestly enough — metadata attribution earns its keep at the volume where manual tagging stops scaling.

Use the durable-metadata approach when recurring revenue, multiple channels, and month-over-month optimization make per-charge source truth actually load-bearing.

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.