Stripe affiliate tracking: how referral attribution works end to end
Updated · 6 min read
Stripe affiliate tracking stores the partner’s referral code when a visitor clicks their link, attaches it to the Stripe customer or subscription at checkout, then uses Stripe webhooks to turn each paid invoice into a commission. Refund webhooks reverse those commissions, and coupon codes can attribute customers who never clicked a link.
The chain from click to commission
Stripe knows who paid and how much, but it has no idea which partner sent that customer. Affiliate tracking is the job of carrying one small piece of information, the partner’s referral code, from the moment someone clicks a link to the moment Stripe records a payment. Every tracking tool does some version of these five steps:
- 1
Click
A visitor follows a partner link such as example.com/?ref=JANE. A script on your site reads the code and records the click.
- 2
Store
The script saves the code in a first-party cookie so it survives the visitor browsing around, leaving and coming back within the attribution window.
- 3
Sign up
When the visitor creates an account, the stored code is linked to their user id in your app.
- 4
Link to Stripe
At checkout, your server attaches the code and the user id to the Stripe Checkout Session, customer or subscription.
- 5
Pay
Stripe sends webhooks for each paid invoice and refund. The tracking tool looks up which partner owns that customer and creates or reverses a commission.
If any link in this chain breaks, the payment still goes through, but the partner is not credited. Most “my referral wasn’t tracked” complaints come from step 4, where the code never reaches Stripe.
Linking the Stripe customer to the partner
Stripe gives you two places to carry the referral: client_reference_id and metadata. They do different jobs, and a robust setup uses both.
| Field | Where it lives | What to put in it |
|---|---|---|
| client_reference_id | Checkout Session only | Your own user or account id, so the payment can be matched to the sign-up |
| metadata on the Checkout Session | Copied into checkout.session.completed | The referral code, for the sign-up event |
| subscription_data.metadata | Stored on the subscription | The referral code and your user id, so every later invoice carries them |
| metadata on the customer | Stored on the Stripe customer | The same, when you create customers without Checkout |
The subscription metadata matters most. A Checkout Session happens once, but invoices keep arriving for months, and each invoice webhook includes the subscription’s metadata. Putting the code there means every renewal can be attributed without a lookup.
The browser side is simple: read the stored code from the tracking script and send it to your server along with the checkout request. The server then creates the session with client_reference_id set to your user id, the code in metadata, and the same values in subscription_data.metadata. Never trust a code for anything beyond attribution; it is a label, not a credential.
In Affiliatops this is how the Stripe integration works: the tracker keeps the code in a first-party cookie for the program’s attribution window (30 days by default), the first code that resolves to an active partner owns the customer, and the integration reads the affiliatops_ref and affiliatops_customer_id metadata keys from Stripe’s webhooks. The integration is webhook-only, so it never needs a Stripe API key. The exact checkout snippet is in the docs.
The Stripe webhooks that create commissions
Webhooks are what make recurring commissions automatic. You add an endpoint in the Stripe dashboard, choose the events to send, and the tracking tool verifies each request with the endpoint’s signing secret before trusting it.
| Stripe event | What it means for the partner |
|---|---|
| checkout.session.completed | Sign-up: links the customer to the referral code in the session |
| customer.subscription.created | Trial started or subscription created; can pay a sign-up or trial bonus |
| customer.subscription.updated / deleted | Plan changes, pauses and cancellations; stops future commissions after cancel |
| invoice.paid | A real payment: creates the commission for the first payment and each renewal |
| invoice.payment_failed | No commission; useful for spotting referred customers at risk of churn |
| invoice_payment.paid | No money. Sent by Stripe API 2025-05-28.basil and later; it ties the payment to its invoice, so a refund can find the commission to reverse |
| charge.refunded | Reverses the commission for the refunded payment, fully or in part |
Three details prevent most double counting and phantom commissions:
- Pick one payment event. invoice.paid and invoice.payment_succeeded fire for the same payment; handling both pays the partner twice.
- Skip zero-amount invoices. Trials and 100% off coupons produce invoices marked paid at $0, which are not revenue.
- De-duplicate by the Stripe event id. Stripe retries webhooks, so the same event can arrive more than once.
For refunds, note that charge.refunded carries the charge’s total refunded so far, not just the latest refund. If a customer receives two partial refunds, the commission reduction must be based on the difference, or the second refund will be counted twice.
Attributing sales by coupon or promotion code
Coupon codes let partners earn from audiences that never click a link: podcast listeners, video viewers, people who heard a recommendation in person. The customer types the partner’s code at checkout, and the code itself identifies the partner.
The catch with Stripe is that the customer-facing promotion code is not always visible in webhook payloads. Stripe usually sends it as a promo_ id rather than the text the customer typed, unless the object was expanded. There are two reliable ways around this:
- Copy the code the customer entered into the session and subscription metadata when you create the checkout, so the webhook carries it as text.
- Create the Stripe coupon with an id or name equal to the partner’s code, so it can be matched even without metadata.
Decide in your terms which wins when a customer has both a referral cookie and a typed code. Many programs let the typed code win, since it is the clearer signal of intent, but only for customers not already attributed to another partner.
Common pitfalls and how to handle them
| Pitfall | What happens | Fix |
|---|---|---|
| Cross-device | Clicks on a phone, signs up on a laptop; no cookie on the laptop | Offer partner coupon codes; let partners claim a customer with evidence you can verify |
| Ad blockers and privacy tools | The tracking script or its requests are blocked | Serve the script from your own domain where possible; capture ?ref on your server as a backup |
| Trials | Checkout happens days after the click, or payment weeks after sign-up | Attach the code at sign-up, not only at payment; commissions come from invoice.paid later |
| Code not passed to Stripe | Payment arrives with no referral; partner not credited | Always set subscription_data.metadata; test with a real partner link |
| Marketing site and app on different domains | Cookie set on one domain is not readable on the other | Pass the code in the sign-up URL or share the cookie across subdomains |
| Customer created outside Checkout | No session, so no client_reference_id | Set the same metadata on the Stripe customer or subscription |
Finally, test the whole chain in Stripe test mode before inviting partners: click a partner link, sign up, pay with a test card, issue a partial refund, then cancel. You should see a sign-up, a commission, a reduced commission and a cancelled subscription, each against the right partner. Once that works, the SaaS demo shows what the resulting dashboard looks like.
Frequently asked questions
Does Stripe have built-in affiliate tracking?
No. Stripe records customers, subscriptions and payments, but it does not track referral links or calculate partner commissions. You need a tracking script plus a tool or your own code that listens to Stripe webhooks.
How do I pass a referral code to Stripe Checkout?
Read the stored referral code in the browser, send it to your server with the checkout request, and create the Checkout Session with the code in metadata and in subscription_data.metadata. Set client_reference_id to your own user id.
Which Stripe webhook should create affiliate commissions?
invoice.paid is the usual choice for subscriptions, because it fires for the first payment and every renewal. Do not also handle invoice.payment_succeeded for the same payments, and ignore invoices paid at $0.
Can affiliates be tracked with Stripe promotion codes?
Yes, but webhooks often carry only the promotion code’s id, not the text. Save the typed code in checkout metadata, or create the coupon with an id or name that matches the partner’s code.
How are affiliate commissions reversed when a Stripe payment is refunded?
The charge.refunded webhook identifies the refunded payment and the amount refunded so far. The tracking tool cancels the commission on a full refund, or reduces it in proportion on a partial refund, and deducts already-paid commissions from the next payout.