Blog

Stripe revenue by traffic source, without cookies: a practical setup

"Which channel brings paying customers?" sounds like a basic question, but most analytics setups answer it badly. The visit happens in the browser, while the payment happens on Stripe's checkout page and is confirmed on Stripe's servers. Something has to connect the two, and that link is where revenue gets lost.

This guide explains the three common approaches and their trade-offs, then walks through a setup that attributes Stripe payments, including subscription renewals, without cookies.

Why revenue goes missing in GA4

Three ways to connect a payment to a visit

ApproachHow it worksWeak spots
Thank-you page eventJavaScript on the success page sends a purchase with the amountLost on closed tabs and blockers; no renewals; easy to double-count
Server event with a client IDYour backend sends the payment with the analytics client ID (e.g. GA4 Measurement Protocol)Needs the client ID, which lives in a cookie; you must store it at checkout
User ID joinThe site tells analytics who the signed-in user is; the payment carries the same user IDOnly works for signed-in buyers; guest checkouts stay unattributed

For SaaS products, where people sign up before paying, the user ID join is the most robust: it needs no cookie, survives closed tabs, and works for every renewal.

The setup, step by step

1. Identify signed-in users

After sign-in, tell your analytics tool the user's ID. Use your internal ID, not an email address. In PageLens:

pagelens.identify(user.id)

2. Pass the same ID to Stripe

When you create a Checkout Session, set client_reference_id. For subscriptions, also copy it into the subscription's metadata, because renewal invoices don't carry client_reference_id:

const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: 'price_123', quantity: 1 }],
  client_reference_id: user.id,
  subscription_data: { metadata: { pagelens_user_id: user.id } },
  success_url: 'https://example.com/welcome',
  cancel_url: 'https://example.com/pricing',
});

3. Listen to the right webhook events

Don't count a subscription checkout from both events, or the first payment is counted twice. A simple rule: ignore checkout.session.completed when mode is subscription and let invoice.paid handle it.

4. Handle the details

What you can answer once it works

Doing it without writing the webhook handler

PageLens revenue attribution does steps 3 and 4 for you: add the webhook URL in Stripe (or Paddle), paste the signing secret, and payments appear by channel, campaign, country and landing page. It handles signatures, retries, subscriptions and zero-decimal currencies, and needs no cookies. For checkouts other than Stripe and Paddle, one pagelens.track('purchase', { revenue: 49, currency: 'USD' }) call on the success page works too.

Try PageLens on your site

Create a free PageLens account: 7-day trial, no card, no cookie banner needed.

Start free trial

Keep reading