Stripe records the charge, not the click. A payment object carries a customer, a card, an amount and a timestamp — no UTM parameters, no click ID, no referrer. You attribute it by joining the payment back to the browsing session that started the journey, using identifiers that survive the gap: a first-party visitor ID, then email, then phone, then the Stripe customer ID.
That join is the whole job. First touch versus last touch, cost per acquisition, which campaign to scale — all of it is downstream of whether a payment ever found its session. Get the join right and the reporting questions get easy. Get it wrong and every model you layer on top is confidently wrong.
Why doesn’t Stripe know which campaign produced a payment?
Because Stripe is a payments system, not an analytics one. It sees the checkout, not the ad click that preceded it by days. A charge contains what your checkout code put into it plus what the card network returned, and unless you deliberately attached marketing identifiers at creation time, none of that describes traffic.
This is not a gap in Stripe — moving money and describing demand are different jobs. The attribution data you want (utm_source, gclid, fbclid, the referrer, the landing page) is a property of a browsing session. The payment is a property of a customer record created much later, often by a server, sometimes by a human in your billing dashboard. Nothing carries the first to the second unless you build the carry.
So when you export a year of Stripe payments and try to group them by channel, the best available grouping is email and date. That is the payments equivalent of watching your ad conversions show up as Direct: the revenue is real, the cause is missing.
What breaks between the ad click and the charge?
Four gaps, and most Stripe setups have all four at once: the checkout runs on a different origin, the payment happens long after the click, the buyer changes device somewhere in the middle, and the click ID never travels with the money.
The checkout is on a different origin
Hosted Stripe Checkout and Payment Links run on Stripe’s own domain. First-party cookies and localStorage are scoped per origin, so anything your site stored about the visit does not exist once the browser lands on the payment page — whatever you want on the far side of that redirect has to be passed explicitly at session-creation time. Embedded card elements keep the buyer on your domain and avoid this one entirely.
The payment lands long after the click
Free trials convert on day 14. Invoices get paid on net-30. A sales-assisted deal closes a month after the demo. Ad platform click windows and browser cookie lifetimes are both far shorter, which is why attributing a purchase weeks after the ad click is structurally different from attributing a same-session ecommerce order.
The buyer changes device
Somebody clicks an ad on a phone during a commute and pays on a laptop that evening. Two browsers, two visitor IDs, one person. Without something joining them, the click and the revenue sit in different rows and neither looks like a conversion.
The click ID never reaches the payment
fbclid and gclid arrive in the landing page URL and live in the browser. Stripe never sees them. Unless something stored those values server-side against the person at the moment of the click, they are gone by the time the card is charged — and a conversion you cannot key on a click ID is one the ad platform will not credit to a campaign.
How do you attribute Stripe payments without a vendor?
Three approaches work: attach your identifiers to the Checkout session, store first-touch attribution against the user record at signup, or join on email after the fact. Each has a real failure mode, and the failure modes are the important part.
1. Pass your identifiers into the Checkout session. When your server creates a Checkout session it can attach a client reference and arbitrary metadata — visitor ID, utm_source, utm_campaign, whatever click IDs the browser still holds. Those values come back on the completed session and on the webhook, so the payment arrives already labelled. (Check Stripe’s current API reference for exact field names and limits before building against them.)
The limit: it only works for payments that pass through code you control, at a moment when the browser still holds the values. Renewals, dunning recovery, invoices sent by a human and subscriptions created in the dashboard never touch that path — in a typical SaaS book of business, that is a lot of revenue.
2. Store attribution against the user at signup, not at payment. Capture UTMs and click IDs on the first pageview and write them onto the user row the moment the account is created. Attribution is then a join from payment to user to first touch, and it keeps working no matter how the charge was created. This is the most durable of the three. The limits: it only covers people who create an account before they pay, it is code you own forever, and it does nothing for revenue you booked before you built it.
3. Join on email after the fact. Export payments, match the customer email against your marketing database, inherit whatever source that record carries. Zero engineering, works retroactively, and it is what nearly everyone actually does.
The limits are real: people pay with a work email and download the lead magnet with a personal one; plus-addressing and private relay addresses break exact matching; a good chunk of your customers will not exist in the marketing database at all. Normalize aggressively — lowercase, trim, strip Gmail dots and plus-suffixes.
Whichever you build, instrument the coverage rate before you trust any percentage split: the share of payments that carried a usable marketing identifier at all. If that is 40%, your channel mix is a claim about 40% of the revenue, and saying so out loud is what separates a real number from a comforting one.
Which identifier actually joins a payment to a session?
Rank them by how much they prove. A first-party visitor ID echoed through checkout is close to certain. A normalized email is strong. A normalized phone is strong. The Stripe customer ID is strong once it has been tied to a person. IP plus user agent is a weak tiebreaker that should never stand alone.
That ranking is the entire design. A join is a claim about identity, and claims differ in strength — a system that treats every match as equally true will quietly merge two people who share an office IP and report nothing wrong.
- Visitor ID echoed back. Your own first-party ID, captured at the click and returned with the payment. Same browser by construction, nothing to infer.
- Email, normalized. Lowercase and trim, then handle the provider quirks before comparing. Works across devices and across months — exactly the gap Stripe opens.
- Phone, normalized to E.164. As strong as email when you collect it, and it survives the work-versus-personal email problem.
- Stripe customer ID. Connects a person’s payments to each other — renewals, upgrades, second purchases — once any one of them has been tied to a session.
- IP plus user agent, within a tight window. A tiebreaker. Shared offices, carrier NAT and household networks all produce false merges, so weight it low and never let it merge alone.
This is the same machinery as visitor identity resolution in any other channel — the Stripe case is just where the conversion event arrives with the least context of all.

What about renewals and second payments?
Decide the rule before you read the report. Once a Stripe customer ID is tied to a session, the identity side is solved — every future invoice for that customer belongs to a known person. Whether a renewal should re-credit the acquisition campaign is a modelling choice, not a tracking one: first touch credits the acquiring campaign for the lifetime, last touch credits whatever touched most recently, which for most renewals is nothing at all. Keep acquisition and expansion revenue in separate columns and the argument disappears.
How does PartialLeads attribute Stripe payments to campaigns?
Stripe connects as a purchase source over OAuth. Every payment then runs through one pipeline: normalize the identity, deduplicate, match to a session in tiers, attach the revenue to the lead, and write both a first-touch and a last-touch attribution row. What you get afterwards is a payment sitting on a journey with the campaign that started it.
The pieces, in the order they run:
Identity normalization. Email is lowercased and trimmed; phone is normalized to E.164 with a country-code fallback from the session’s geo. The comparison happens on canonical values, not on whatever the buyer typed.
Deduplication. Payments are idempotent on client, source, order ID and status phase. A redelivered webhook cannot double-count, and a refund gets its own row that nets against the paid one rather than vanishing from the ledger.
Tiered matching. The payment matches to a session by visitor ID echo first, then email, then phone, then IP — with a confidence on each tier, so a strong match and a weak one are never the same fact. Behind it sits an identity graph mapping normalized email, phone, Stripe customer ID and IP to sessions, and a cluster resolver that unions a person’s sessions across visitor ID, email, phone, IP plus user agent, a device fingerprint and shared click IDs. That is what resolves the phone-click-then-laptop-payment case.
Attribution rows. Every matched payment writes a first-touch and a last-touch row, each snapshotting UTMs, click IDs, referrer, landing page and time to purchase. The display layer uses last-non-direct with a 28-day decay window, matching Meta’s standard so the reports argue with your ad platform less often.
Unmatched payments still land. A payment that resolves to nobody is recorded as unmatched rather than dropped, and re-matches later when identity arrives — with a manual re-match for the ones you can identify by hand. The unmatched wedge is a visible number, not a rounding error inside a total.
Where you see it working: the Purchases ledger with its matched and unmatched split, the Revenue column and journey timeline on the lead, and the Attribution report showing First Touch, Last Touch and PartialLeads Resolved side by side with a Match Rate KPI and a Revenue by Match Quality breakdown — how to read an attribution report walks through what each model proves.
Matched payments also fan out server-side to Meta, Pinterest, TikTok and Google Ads with a deterministic event ID, so the ad platform gets the conversion it never saw — the delayed-subscription version of that path is covered in tracking SaaS trial conversions to Meta Ads.
The honest constraint. Ingestion is the part a vendor controls: every payment Stripe reports is ingested and deduplicated. Matching is the part nobody fully controls. If a buyer never visited your site with the tag installed — a payment link emailed cold, an invoice paid by someone in accounts payable — there is no session to match to, and the payment is unmatched revenue rather than a campaign row. That fix is collection, not software: the more of a person you capture before the payment, the more of your revenue resolves. The same logic governs attributing ecommerce revenue to the ad click on the storefront side.

| What breaks | The mechanism | Where you see it in the dashboard |
|---|---|---|
| The Stripe charge carries no campaign data | Stripe connects as a purchase source; each payment is normalized and matched to a session in tiers | Purchases ledger, matched vs unmatched |
| The payment lands days or weeks after the click | First-touch and last-touch rows written per matched payment, with time to purchase snapshotted | Attribution report, Time to Purchase |
| Clicked on a phone, paid on a laptop | Identity cluster unions sessions by visitor ID, email, phone, IP plus user agent, device hash and click ID | Journey timeline on the lead |
| The payment matches nobody | Unmatched payments are recorded and re-match when identity arrives; manual re-match available | Purchases ledger, unmatched wedge |
| Refunds overstate attributed revenue | Refund rows carry their own status phase and net against the paid row | Purchases ledger |
| Nobody trusts the channel split | First, last and resolved models reported side by side with match quality | Attribution report |
Tell us what's broken. We'll fix your tracking — free.
Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.
Sources
- Meta for Developers — Conversions API overview: https://developers.facebook.com/docs/marketing-api/conversions-api
- Meta for Developers — Customer information parameters: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters
- Meta for Developers — Deduplicate Pixel and Conversions API events: https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events