How to set up Stripe metadata attribution

Write a visitor ID and UTM fields into Stripe metadata for Checkout, Payment Links and subscriptions, and see which copies Stripe makes and which it skips.

Muzahid Maruf — Founder of TrackRev.io

Muzahid MarufUpdated

Revenue attribution · 8 min read
On this page
  1. 01What Stripe metadata can hold
  2. 02Which Stripe objects inherit metadata
  3. 03Stripe metadata attribution setup, step by step
  4. 04What TrackRev does with the visitor ID
  5. 05When metadata isn't enough

Explore with AI

Opens this article inside the chosen assistant with a ready-made prompt.

Stripe metadata attribution setup means writing a visitor ID and 6 UTM fields into the metadata of the Stripe objects that carry a payment, so one report can show which channel pays your MRR.

Stripe forwards metadata in only 4 documented cases, so a source set on the Checkout Session alone reaches none of the invoices and charges that follow.

Key takeaways

  • Stripe allows 50 metadata pairs per object, with keys up to 40 characters and values up to 500, so a visitor ID plus 6 UTM fields uses 7 slots.
  • A Checkout Session's metadata is not copied to its PaymentIntent or Subscription. Set payment_intent_data.metadata or subscription_data.metadata yourself.
  • Invoices created from June 29, 2023 hold a snapshot of the subscription's metadata, while the renewal charge holds none, so read renewals from the invoice.
  • client_reference_id holds up to 200 characters and is the per-click field for Payment Links, where UTM codes reach only your redirect URL.
  • Stripe Search finds metadata on 7 object types within 1 minute, but not on Checkout Sessions.

What Stripe metadata can hold

Stripe keeps metadata on Customers, Checkout Sessions, PaymentIntents, Charges, Subscriptions, Invoices and other objects.

I'd start with a single key, vid, pointing at visit history you control, and copy UTM fields onto Stripe only to see the source in webhook payloads. Stripe's metadata guide recommends the same split for bigger records.

FieldLimitConsequence
Metadata pairs per object50A visitor ID plus 6 UTM fields uses 7
Metadata key40 charactersutm_campaign takes 12
Metadata value500 charactersParse landing URLs into discrete values
Readable withSecret keys onlyNot from Stripe.js in the browser
client_reference_id200 charactersFits a visitor ID, while campaign data goes in metadata
UTM codes on a Payment Link5 parameters, 150 characters eachLetters, digits, dashes and _ only
Checkout Session lifetime30 minutes to 24 hours, default 24Keys written at creation can be a day old at payment

Limits from Stripe's metadata, Checkout Session and Payment Links documentation, October 2026.

Which Stripe objects inherit metadata

Metadata doesn't copy to related objects unless one of 4 documented exceptions applies, and Customer metadata never copies down, so look it up by customer ID.

Three exceptions are one-time snapshots, which leave the copy unchanged when you edit the parent later. The 4th shows the subscription's current values.

ExceptionFromToWhat Stripe copies
1PaymentIntentChargeA snapshot when the charge is created
2Payment LinkCheckout SessionA snapshot when the Session is created
3SubscriptionInvoiceA snapshot in the invoice's parent details
4SubscriptionInvoice line itemThe current values, for line items of type subscription

Copy rules from Stripe's metadata documentation, October 2026.

Worked example: a $49 monthly plan

A newsletter reader buys a $49 plan through Stripe Checkout and stays 12 months, which produces 12 invoices and 12 charges worth $588. Metadata on the Checkout Session alone reaches 0 of those 24 objects. Adding subscription_data.metadata puts the source on the Subscription and all 12 invoices, and the 12 charges still carry none.

Per Stripe's Invoice reference, parent.subscription_details.metadata is immutable once the invoice is finalized and exists only on invoices created from June 29, 2023. Fix a mistyped utm_campaign after the 3rd invoice and those 3 invoices keep the typo.

A Charge in the current Stripe API has no invoice field, so report from invoices or the invoice.paid event, or go from a charge through its PaymentIntent to the Invoice Payments API.

Stripe metadata attribution setup, step by step

Use a Stripe sandbox. The test card 4242 4242 4242 4242, with any future expiry date and any 3-digit CVC, completes a payment, per Stripe's testing guide.

Step 1: Capture the source on your own domain

Read the 5 UTM parameters on the first page view and store them under a visitor ID in a first-party cookie or your database. Stripe never sees the ad that sent the visitor, because your server creates the Checkout Session.

Per WebKit's tracking prevention notes, Safari's Intelligent Tracking Prevention deletes cookies created in JavaScript after 7 days without user interaction, so set the cookie from your server where you can. Why UTM parameters get stripped covers the other leaks.

Step 2: Pick the keys once

Pick short, stable names, since webhooks and backfills depend on them. This schema uses 9 of the 50 slots.

KeyExample valuePurpose
vidA 36-character UUIDJoin key to your stored visit history
ft_source, ft_medium, ft_campaignnewsletter, email, launch-2026First touch, written once and never overwritten
lt_source, lt_medium, lt_campaigngoogle, cpc, brand-2026Latest touch before checkout
click_ida gclid or fbclidPlatform click ID for sending conversions back
first_touch_at2026-10-02T14:08:31ZTime of the first visit in ISO 8601

Store both touches: 2 sets of 3 keys cost 6 slots and leave the credit rule, first-touch, last-touch or linear, for report time, as the attribution model comparison lays out.

Step 3: Write the keys onto the Checkout Session

In subscription mode, write 3 fields. The Session's own metadata (1 key, vid) arrives with checkout.session.completed. subscription_data.metadata (7 keys) lands on the Subscription and, through the snapshot, on every invoice.

client_reference_id takes up to 200 characters and comes back on the Session. In payment mode use payment_intent_data.metadata, which reaches the Charge as a snapshot, per the Checkout Session reference. The Stripe Checkout attribution guide covers the redirect side.

Checkout Session in subscription mode (Node)
// visitorId and attr come from your own first-party capture (step 1)const session = await stripe.checkout.sessions.create({  mode: "subscription",  line_items: [{ price: "price_123", quantity: 1 }],  client_reference_id: visitorId,        // up to 200 characters, echoed on the Session  metadata: { vid: visitorId },          // lands on the Checkout Session  subscription_data: {    metadata: {                          // lands on the Subscription, then on each invoice      vid: visitorId,      ft_source: attr.firstSource,      ft_medium: attr.firstMedium,      ft_campaign: attr.firstCampaign,      lt_source: attr.lastSource,      lt_medium: attr.lastMedium,      lt_campaign: attr.lastCampaign,    },  },  success_url: "https://example.com/welcome",});// mode: "payment" takes payment_intent_data: { metadata: { ... } } instead.

Stripe's tracking page says the 5 UTM codes on a Payment Link are passed to your redirect URL after payment, and only when the link's confirmation behavior is a redirect. Stripe doesn't describe saving them on the payment.

Two routes do reach it.

Route 1 is a Payment Link per channel, created through the API with metadata on the link, which copies to every Checkout Session, plus the nested metadata fields for the PaymentIntent or Subscription underneath. It suits 3 to 5 channels.

Route 2 is a per-click ID: append ?client_reference_id=VISITOR_ID to the link, using letters, digits, dashes and _ only, 200 characters at most. Stripe silently drops anything else, such as an ID with a dot or colon.

The Payment Link reference and the Payment Links attribution guide cover the rest.

One Payment Link per channel, with a recurring price (curl)
curl https://api.stripe.com/v1/payment_links \  -u "<<YOUR_SECRET_KEY>>:" \  -d "line_items[0][price]=price_123" \  -d "line_items[0][quantity]=1" \  -d "metadata[lt_source]=newsletter" \  -d "subscription_data[metadata][lt_source]=newsletter"

Step 5: Read the metadata back

Every Stripe event carries its object with that object's metadata, and most attribution work needs only these 4 events.

EventObject in the payloadWhere the source sits
checkout.session.completedCheckout Sessionmetadata and client_reference_id
customer.subscription.createdSubscriptionmetadata set through subscription_data
invoice.paidInvoiceparent.subscription_details.metadata, the snapshot
charge.succeededChargemetadata, payment mode only, copied from the PaymentIntent

Event names from Stripe's event reference, October 2026.

Free trials have no charge yet, so trial-to-paid rates by channel start at customer.subscription.created. Count delayed payment methods on checkout.session.async_payment_succeeded. Verify the Stripe-Signature header and key rows by subscription ID, since deliveries repeat and arrive out of order.

BehaviorLimit
Retries of a failed webhook delivery, live mode3 days, exponential backoff
Retries of a failed sandbox delivery3 attempts over a few hours
Resending an event from the Stripe Dashboard15 days
Resending an event from the Stripe CLI30 days
Metadata search coverage7 object types, no Checkout Sessions
Clauses in one search query10, joined by AND or OR but not both
Time until new data is searchableUnder 1 minute
Search rate limit20 reads a second
Oldest API version that supports Search2020-08-27

Delivery and search limits from Stripe's webhook and search documentation, October 2026.

Stripe Search takes metadata["vid"]:"VALUE" on charges, customers, invoices, PaymentIntents, prices, products and subscriptions, matching exactly and ignoring case. Stripe points analytics workloads to Stripe Sigma and bulk exports to Data Pipeline, and its webhook documentation has the signature details.

Find active subscriptions from one source (curl)
curl -G https://api.stripe.com/v1/subscriptions/search \
  -u "<<YOUR_SECRET_KEY>>:" \
  --data-urlencode 'query=metadata["lt_source"]:"newsletter" AND status:"active"'

Grouping active subscriptions by lt_source gives MRR by channel.

lt_sourceActive subscriptionsPlanMRRShare of MRR
newsletter12$49$58836.2%
google8$99$79248.7%
affiliate5$49$24515.1%
Total25$1,625100%

Illustrative figures, not customer data.

Step 6: Test the chain before you trust it

Run a subscription checkout in the sandbox, then retrieve the Checkout Session, the Subscription, the first invoice and the charge with stripe get from the Stripe CLI. The Session shows 1 key and the Subscription shows 7.

The invoice shows the same 7 in its snapshot, and the charge shows none. Repeat with a payment-mode Session, where the charge should carry the PaymentIntent's keys.

Then open a Payment Link with ?client_reference_id=test-123 and confirm the value on the completed Session.

What TrackRev does with the visitor ID

TrackRev is SaaS affiliate software with revenue attribution built in. It reads Stripe with a read-only restricted key, so it never writes metadata.

Its pixel keeps a visitor ID in a first-party cookie named vid and adds it to Payment Links, Buy Buttons and Pricing Tables on your pages.

For Checkout Sessions your server creates, pass the cookie value as the Session's reference ID or set metadata.vid on the PaymentIntent.

The Stripe sync finds the Checkout Session through the charge's PaymentIntent, which Stripe documents for payment-mode Sessions only, so Subscription checkouts need identify().

OrderWhere each sync looks for the visitor ID
1metadata.vid on the charge
2client_reference_id on the Checkout Session
3metadata.vid on the Checkout Session
4The buyer's email, if you called identify() on your site

Lookup order in TrackRev's Stripe sync.

Renewals inherit the first order's visitor through the subscription ID, so month 12 credits the original channel without any subscription metadata. The sync runs hourly.

Call identify() at signup too, since the pixel writes its cookie from JavaScript and Safari's 7-day cap applies. Revenue figures need a paid plan from Indie at $29 a month (pricing), and setup is on the Stripe integration page.

When metadata isn't enough

Metadata holds a snapshot of 1 or 2 touches taken at purchase, so keep the full history of visits and link clicks under the visitor ID, as in the multi-touch attribution setup guide.

If you only match each payment to a row in your orders table, a single reference ID is enough and 49 slots stay empty. Plan changes in the Stripe customer portal are covered in Stripe Customer Portal attribution.

Sales-led deals sent as Stripe Quotes can carry the source in the quote's subscription data, which lands on the Subscription on acceptance.

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 founded TrackRev.io, SaaS affiliate software with no limit on tracked revenue, and Contant.io. He writes about affiliate programs.

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

Stop guessing where your revenue comes from.

Set up TrackRev in about five minutes. The free plan covers 1,000 events a month, no card needed.

Start free