Payments

Stripe Checkout UTM attribution walkthrough

Ownlytics team · · 6 min read

To attribute a Stripe sale to a traffic source, the visitor's id has to travel with the payment. Tag the visit on your own site, copy the id into the Checkout Session metadata, and read it back when the payment arrives.

https://yoursite.com/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=launch-week
utm_source
Where the click came from: a site, a list, a person.
utm_medium
The kind of channel: email, social, cpc.
utm_campaign
The specific push this link belongs to.

Fig.One tagged link. Keep the names lowercase and consistent, or the same campaign splits into three rows.

UTM tags alone do not survive the trip. They live on your page, and the buyer pays on a different domain. The fix is not complicated, but there are three separate paths into Stripe, and each one needs slightly different handling.

Why do UTM tags disappear at checkout?

A visitor lands on yoursite.com/pricing?utm_source=newsletter. They click buy and end up on Stripe's hosted page. Stripe records the payment. It does not receive the query string you started with, and it does not know the visitor's browsing history.

So you need a bridge: an identifier created on your site and handed to Stripe, so the payment can be joined back to the visit. Ownlytics' tracking script creates that identifier as a first-party id (cookies named datacheap_visitor_id and datacheap_session_id on your own domain). The rest of this post is about getting that id onto the payment. For the general version of the problem, see why your payment dashboard can't tell you where buyers came from.

Which path does your checkout use?

There are three, and many sites use more than one.

  1. Payment Links. You paste a buy.stripe.com link into a button. Nothing for you to do: the Ownlytics script adds client_reference_id to every buy.stripe.com link on your site, and that id comes back with the payment.
  2. Checkout Sessions created by your server. You call Stripe's API yourself. Here you pass the visitor and session ids as metadata.
  3. Success page. The buyer returns to your site with Stripe's session id in the URL, and the script links the two. This works as a backup for either of the paths above.

What do you put in the Checkout Session?

Read the two cookies on the server, then put them in metadata. For subscriptions, also put the same object in subscription_data.metadata so later invoices keep the link.

const cookie = (name) =>
  request.headers.get('cookie')?.match(new RegExp('(?:^|; )' + name + '=([^;]+)'))?.[1];

const metadata = {
  ownlytics_visitor_id: cookie('datacheap_visitor_id'),
  ownlytics_session_id: cookie('datacheap_session_id'),
};

const session = await stripe.checkout.sessions.create({
  mode: 'payment', // or 'subscription'
  line_items: [{ price: 'price_...', quantity: 1 }],
  metadata,
  subscription_data: { metadata }, // subscriptions: renewals keep the visitor
  success_url: 'https://yoursite.com/thanks?session_id={CHECKOUT_SESSION_ID}',
});

The success_url with {CHECKOUT_SESSION_ID} is the backup path. If the metadata is ever missing, a buyer returning to your thank-you page still lets the script link the session. Custom PaymentIntent flows work too, as long as the intent carries the same metadata.

How does Ownlytics read it back?

You connect Stripe per website with a restricted key. The permissions are pre-filled when you follow the link in the website's setup: read access to Checkout Sessions, Charges, PaymentIntents, Invoices, Subscriptions, Products, Prices and Customers, plus write access to Webhook Endpoints. The write permission is there so "Instant updates" can create a webhook for you. You can choose every payment in the account, or only specific products. Past sales are imported when you connect.

The full setup lives in the revenue attribution docs, and the Stripe overview shows the result. If you want code for the landing side (capturing UTM tags into a cookie), the UTM tracking with Stripe Checkout guide covers it for Next.js.

What if the id never reaches Stripe?

Sometimes it won't. A buyer clears cookies, pays on another device, or you have an old Checkout Session flow nobody updated. Ownlytics falls back in order: the visitor id in metadata, the thank-you page, an email the visitor gave you through identify, and last, checkout timing.

Timing is the weakest link. If exactly one visitor clicked a Stripe checkout link in the three hours before the sale, the sale is credited to them. When several people clicked, it waits 30 minutes and credits the only one who came back from the payment page, or the only one who clicked in the last 30 minutes. Otherwise the sale stays unattributed, with a note saying why. Timing matches are marked as estimated, and real metadata replaces them when it arrives.

Treat estimated rows as hints. If a large share of your revenue is estimated, fix the metadata instead of trusting the guess.

yourapp.comToday7d30d9 online
SourceVisitorsSalesRevenue
Ggoogle.com1,9006$294
Xx.com2,4005$245
@newsletter6403$147
Ynews.ycombinator.com8102$98
↗Direct4201$49

Fig.Stripe sales by source once the visitor id reaches checkout. Sample data, not a real site.

Example (made-up numbers for illustration)

You sell a $49 template. Over a week you get 10 sales, and you want to know which links earned them.

SourceSalesHow the id arrived
Newsletter link4Payment Link, auto-tagged
Blog post3Server-created session, metadata
Social post2Success page backup
Unattributed1No id, several checkout clicks

The one unattributed sale is normal. What matters is that 9 of 10 can be explained, and that the unexplained one says why. If it were 5 of 10, the metadata is the thing to debug.

When this is not worth the effort

If you get a few sales a month and know every buyer by name, a spreadsheet and a "how did you hear about us" field is enough. Metadata plumbing pays off when you post in several places and can't remember which one worked. Also note that attribution depends on the visitor passing through a page with the script before paying. Sales that start entirely off your site (an invoice you send by hand, for instance) have no visit to credit.

At a glance

QuestionAnswer
Payment LinksScript adds client_reference_id automatically
Server-created sessionsPass ownlytics_visitor_id and ownlytics_session_id as metadata
SubscriptionsAlso set subscription_data.metadata; renewals inherit the first payment's visitor
BackupSuccess URL with {CHECKOUT_SESSION_ID}
Last-resort matchCheckout timing, marked as estimated
Next stepCompare first touch vs last touch once data flows

Once metadata is flowing, you can see how refunds change the picture. If you want to see the finished view before wiring anything, the live demo shows a sample site.

FAQ

Do I need to change my Stripe Payment Links?

No. The tracking script adds client_reference_id to every buy.stripe.com link on your site. You can optionally set the after-payment redirect to a thank-you URL containing {CHECKOUT_SESSION_ID} as a backup.

Does this work for subscriptions?

Yes. Pass the same metadata in subscription_data.metadata. The first paid invoice is credited to the checkout's visitor, and every renewal inherits that source.

What Stripe permissions does the key need?

A restricted key with read access to Checkout Sessions, Charges, PaymentIntents, Invoices, Subscriptions, Products, Prices and Customers, plus write access to Webhook Endpoints for instant updates. The setup link pre-fills these.

What if a buyer clears their cookies before paying?

The visitor id is gone, so Ownlytics tries the thank-you page, then an identified email, then checkout timing. The last one is an estimate and is labeled as such.

See the live demo · View pricing

Keep reading