Stripe Payment Links attribution: find the channel behind each sale

Stripe stores no campaign or referrer for a Payment Link sale. See what it does keep, 3 ways to tag each sale with its channel, and how renewals inherit it.

Muzahid Maruf — Founder of TrackRev.io

Muzahid MarufUpdated

Stripe attribution · 7 min read
On this page
  1. 01What Stripe keeps from a Payment Link sale
  2. 02Why Google Analytics 4 and Safari lose the source
  3. 03Three methods for Payment Links attribution
  4. 04Subscriptions, delayed payments and refunds
  5. 05What TrackRev reads from Payment Link sales
  6. 06When your own webhook is enough

Explore with AI

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

To attribute a Stripe Payment Links sale, put the channel in the client_reference_id (200 characters) or in link metadata (50 keys, 500-character values), the only fields that reach the sale.

UTM codes on a link reach only your redirect URL, because no Stripe API object has a field for them.

The Checkout Session has no referrer field either, and you publish no pages on buy.stripe.com, where the Google tag for Google Analytics 4 would have to run.

Key takeaways

  • UTM codes reach only your redirect URL and never the Checkout Session, so the source must ride in client_reference_id or link metadata.
  • Stripe's limits: 150 characters per UTM value, 50 metadata keys per object, and a 200-character client_reference_id that a 36-character UUID fits.
  • A renewal creates no Checkout Session, so copy the channel onto the subscription after payment 1.
  • TrackRev is affiliate software with attribution built in, and its pixel tags buy.stripe.com links on its own, and revenue figures start at $29 a month.

Stripe's page on tracking a payment link lists 5 UTM codes and the client_reference_id parameter. UTM codes work only with redirect confirmation, and they are appended to that redirect URL.

The Checkout Session object has no UTM field, and Stripe's OpenAPI spec for API version 2026-09-30 has no utm_ field on any object.

FieldWhere you set itWhere Stripe puts itLimit or catch
UTM codesQuery string of the link URL (5 codes)Appended to your redirect URL after paymentRedirect confirmation required; 150 characters per value; no Session field keeps them
client_reference_idLink URL, Buy Button or pricing table attributeCheckout Session and the checkout.session.completed event200 characters; letters, digits, dashes, the _ character; invalid values dropped silently
Link metadataPayment Link API objectCopied onto each Checkout Session the link creates50 keys, 40-character key names, 500-character values; same for every buyer
payment_intent_data.metadataPayment Link API objectPaymentIntents the link creates in payment modeOne-time prices only
subscription_data.metadataPayment Link API objectSubscriptions the link createsNeeds at least 1 recurring price
Link analyticsAutomaticPayments and analytics tab in the Stripe DashboardViews, sales and revenue only; up to 18 hours late; unavailable for recurring prices

Sources: Stripe's payment link tracking page, metadata guide and API reference for creating a payment link, October 2026.

Link metadata is the same for every buyer, so it names a channel only when each channel has its own link.

Setting it is an API job, since the Stripe Dashboard steps on Stripe's tracking page cover only the client reference ID.

Why Google Analytics 4 and Safari lose the source

Google's cross-domain setup needs the same G- tag ID on every domain in the measurement, and you publish no pages on Stripe's, so Google Analytics 4 can log the click on your page but never the purchase.

The GA4 revenue-by-channel guide shows the symptoms.

In Safari, WebKit's Intelligent Tracking Prevention deletes script-written cookies and localStorage after 7 days without interaction, so a newsletter reader who buys 8 days after the click arrives with no stored ID.

A Set-Cookie header from your own server falls outside that rule, so I'd set the ID there. The Safari ITP post covers the rest.

Pick by volume: 2 or 3 channels fit the first method, affiliates, many placements and renewals need the second, and the third adds UTM capture to a thank-you page you already run.

Create a Payment Link per placement and read Stripe's per-link numbers in the Payments and analytics tab, which skips links with recurring prices, so this method suits one-time products.

To keep an exportable reference, click the arrow beside Copy, choose URL parameters, then Client reference ID, and enter a value such as nl-2026-10 for the October newsletter.

An example with made-up numbers: a $49 template pack sells through 3 URLs, with 14 sales on the newsletter link, 6 on a Google Ads link and 2 on the affiliate link.

Revenue is $686, $294 and $98, which sums to $1,078. Refund 2 of the 14 newsletter sales and that link's net revenue falls to 12 × $49 = $588.

Against a made-up $250 of Google Ads spend, the 6 sales return $294, or $1.18 per $1. Every new placement costs a new URL.

A visitor ID joined on the Stripe webhook

On the first visit, create a UUID, store it in a first-party cookie and save the landing page, referrer and UTM codes against it. Add the UUID as client_reference_id to each Payment Link; its 36 characters fit the 200-character limit.

Your pages: create the ID and tag the links
// On your own pages, before the visitor can click a Payment Linkfunction visitorId() {  var m = document.cookie.match(/(?:^|; )cid=([^;]+)/);  if (m) return m[1];  var id = crypto.randomUUID(); // 36 characters: hex digits and dashes  document.cookie = "cid=" + id + "; Max-Age=31536000; Path=/; SameSite=Lax; Secure";  return id;} var cid = visitorId(); // Send the landing context; your endpoint keeps the first row per IDnavigator.sendBeacon("/api/visits", JSON.stringify({  cid: cid,  landing: location.href,  referrer: document.referrer})); document.querySelectorAll('a[href^="https://buy.stripe.com/"]').forEach(function (a) {  var u = new URL(a.href);  u.searchParams.set("client_reference_id", cid);  a.href = u.toString();});

On the paid event, the handler joins the reference to the stored visit and writes the channel to 2 Stripe objects: the subscription or PaymentIntent, plus the customer.

Your webhook: resolve the channel
// Inside your Stripe webhook handler, after stripe.webhooks.constructEvent()// Handle 2 events: ACH debits and other delayed methods fire the async one days laterconst paidEvents = ["checkout.session.completed", "checkout.session.async_payment_succeeded"]; if (paidEvents.includes(event.type)) {  const session = event.data.object;  if (session.payment_status === "unpaid") return; // delayed method: wait for the async event   const visit = await db.visits.findFirst(session.client_reference_id); // may be null  const metadata = {    channel: visit ? visit.source : "unknown", // your own label, e.g. "newsletter"    campaign: visit ? visit.campaign : "none",    cid: session.client_reference_id || "none",  };   // Renewals inherit subscription metadata; one-time sales use a guest customer,  // so the PaymentIntent is the object to write to there  if (session.mode === "subscription") {    await stripe.subscriptions.update(session.subscription, { metadata });  } else if (session.payment_intent) {    await stripe.paymentIntents.update(session.payment_intent, { metadata });  }  if (session.customer) {    await stripe.customers.update(session.customer, { metadata });  }}

Test locally with stripe listen from the Stripe CLI. The Buy Button docs and pricing table docs take the same reference as a client-reference-id attribute, with the same 200-character limit.

A redirect page that records the Checkout Session

Set the confirmation behavior to redirect and put {CHECKOUT_SESSION_ID} in the URL. Per Stripe's post-payment page, Stripe fills in the session ID and appends the link's UTM codes (up to 5), so your thank-you page gets both.

The page records a sale only if the buyer's browser loads it, and 2 buyers on one tagged URL look identical. The free UTM builder makes the URLs.

Subscriptions, delayed payments and refunds

client_reference_id sits on the Checkout Session, and a renewal creates none: month 2 is an invoice.

Per the Invoice object reference, each invoice carries a snapshot of the subscription metadata taken at finalization, for invoices created on or after June 29, 2023 (in API version 2026-09-30 it sits at parent.subscription_details.metadata). Write the channel to subscription metadata.

Suppose 10 buyers from one newsletter take a $29 monthly plan for 12 months. That is 10 × $29 × 12 = $3,480, and the first payments are $290 of it, about 8%.

Crediting only the first charge makes the newsletter look 12 times smaller. The subscription LTV attribution guide reports lifetime revenue by channel.

Stripe says bank debits and vouchers can take 2 to 14 days to confirm, and a refund typically takes 5 to 10 business days to reach a statement.

Count revenue on a paid status and drop it when you issue the refund.

TrackRev is SaaS affiliate software, and its pixel is the built-in tracking layer (Indie at $29 a month for 1 site, Growth at $299 a year for up to 10).

On pages where the pixel runs, it adds its 36-character visitor ID as client_reference_id to every buy.stripe.com link and to Buy Button and pricing table embeds.

A read-only restricted Stripe key lets TrackRev find the Checkout Session behind each paid charge and credit the click behind that reference. With no ID, matching falls back to the email passed to identify().

SettingValue
Pixel link sweepsAt load, at 0.8, 1.5 and 2.5 seconds, and on click
Stripe sync and windowHourly poll, or seconds with the optional webhook; 90-day attribution window
Default modelLast touch; first touch and linear are 1 dropdown away
Renewals and refundsRenewals inherit the original click, and a refund removes the credit on the next sync
Visitor cookieWritten from JavaScript with a 1-year lifetime; Safari's 7-day rule can delete it sooner
Paid plans, all with Revenue AttributionIndie $29 a month (1 workspace, 1 tracked domain), Growth $299 a year (up to 10 workspaces, 10 tracked domains)
Free plan50 links and 1,000 tracked events a month, no revenue figures
Where it adds no IDLinks pasted into email, DMs or a bio, and links that already carry a client_reference_id

Source: TrackRev's pixel script and plan limits, October 2026.

When your own webhook is enough

One product on 2 or 3 links needs no tool, and an existing webhook endpoint makes the visitor-ID method a few dozen lines plus a visits table.

Renewals, refunds and delayed payments are the harder part, and affiliates turn each one into a commission change, which is where I'd pay for a tool.

Checkout Sessions you create yourself take metadata at creation, and the Checkout attribution guide and metadata setup guide cover that route.

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