Short answer
Stripe doesn't record where a buyer came from, so you have to pass it in. On Payment Links, add client_reference_id to the URL. On Checkout Sessions your server creates, set metadata (plus subscription_data.metadata for renewals). On PaymentIntents, set metadata. Store a visitor id there, match it to that visitor's source, and sum revenue per source.
Why the Stripe dashboard can't tell you
Stripe sees the checkout, not the website before it. A payment carries the amount, the customer, the product and whatever you attached to it. It has no referrer, no landing page and no campaign. The visit that started it happened on your site, maybe days earlier, so the link between the two has to be made by you.
The usual way is to give each visitor an id when they arrive, record their source against that id, and hand the id to Stripe at checkout. When the payment comes back (by webhook or API), you look the id up and credit the source. How you hand it over depends on which kind of Stripe checkout you use.
| Your setup | Where the id goes | Comes back on |
|---|---|---|
Payment Links (buy.stripe.com) | client_reference_id URL parameter | The Checkout Session, in checkout.session.completed |
| Checkout Sessions created by your server | metadata, plus subscription_data.metadata or payment_intent_data.metadata | The session, then the subscription's invoices or the PaymentIntent |
| Elements / your own PaymentIntents | metadata on the PaymentIntent | The PaymentIntent and its charge |
1. Payment Links
Payment links are the easiest to start with and the easiest to lose the source on, because nothing runs on your server. Stripe accepts a client_reference_id in the link's URL and attaches it to the Checkout Session it creates. It must be letters, numbers, dashes or underscores, up to 200 characters; invalid values are silently dropped.
https://buy.stripe.com/abc123?client_reference_id=v_8f3k2m9q
Your site adds the visitor's id to the link before the click (a few lines of JavaScript on every buy.stripe.com link), and your webhook reads client_reference_id from checkout.session.completed.
Stripe also accepts utm_source, utm_medium, utm_campaign, utm_content and utm_term on a payment link. Its docs describe them as being passed on to your after-payment redirect URL, so they only help if the link redirects back to your site and something there records them. A visitor id is more robust: it carries the whole visit, not just the tags on the last link.
2. Checkout Sessions created on your server
When your server calls stripe.checkout.sessions.create, read the visitor id (from a first-party cookie, for example) and put it in metadata. Stripe metadata holds up to 50 keys, with keys up to 40 characters and values up to 500.
One detail catches most people: metadata does not copy itself to related objects. Session metadata stays on the session. For a subscription, also pass subscription_data.metadata; Stripe then snapshots it onto every invoice the subscription creates (parent.subscription_details.metadata), so renewals can be credited too. For one-time payments, payment_intent_data.metadata puts it on the PaymentIntent, which is where refunds point.
const metadata = { visitor_id: visitorId };
await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: 'price_123', quantity: 1 }],
metadata,
subscription_data: { metadata }, // renewals carry it on each invoice
success_url: 'https://yourapp.com/thanks?session_id={CHECKOUT_SESSION_ID}',
});
The {CHECKOUT_SESSION_ID} placeholder is worth keeping even when metadata is set: when the buyer lands on your thank-you page with the session id in the URL, the page itself can link that session to the visitor. It's a useful backup for buyers whose cookie was missing.
Full code for capturing UTM tags and reading them back in a webhook is in UTM tracking with Stripe Checkout.
3. Elements and custom PaymentIntents
If you build the payment form yourself with Stripe Elements, your server creates the PaymentIntent, so add the same metadata there. Stripe copies PaymentIntent metadata onto the charge it creates, so it's also visible on the payment in the dashboard.
Turning ids into revenue by source
With the id on each payment, the rest is bookkeeping, and it's where most homemade setups stop being fun:
- Which visit gets the credit? The first visit, the last, or the last one that wasn't Direct? Each answers a different question. See revenue attribution for the trade-offs.
- Renewals should be credited to the source of the first payment, or a channel that brought a yearly subscriber looks worthless after month one.
- Refunds should come off the source that got the sale.
- Currencies need converting before you add them up.
- Missing ids (blocked scripts, a buyer who switched devices) need a fallback or an honest "unattributed".
How DataBull does it
DataBull connects to Stripe with a restricted key and does all of the above. Past payments are imported when you connect, and new ones arrive by webhook.
- Payment links are tagged for you. The tracking script adds
client_reference_idto everybuy.stripe.comlink on your site. Nothing to change. - Server checkouts pass two fields. Add
databull_visitor_idanddatabull_session_id(read from DataBull's cookies) asmetadataandsubscription_data.metadata. If you're coming from DataFast,datafast_visitor_idworks too. - The thank-you page is a backup. Set the success URL to
/thanks?session_id={CHECKOUT_SESSION_ID}and the script links the session to the visitor who lands there.
Each payment is then credited to a visit with the model you choose per website (last non-direct by default, or first or last touch). Renewals inherit the source of the first payment, refunds come off it, and sales that couldn't be linked say why. The dashboard shows revenue by channel, referrer, campaign, landing page and country, and a Telegram message can name the source of every sale. Polar, Dodo Payments and Creem work the same way. Setup details are in the revenue docs.
Checked in October 2026 against: Stripe: Payment Links URL parameters and UTM codes, Stripe: Metadata (limits, what copies where), Stripe API: Create a Checkout Session. Spotted something out of date? Tell us.