Stripe affiliate tracking: how a SaaS ties a sale to an affiliate
Stripe affiliate tracking means knowing which affiliate sent each Stripe payment, so you pay the right person the right amount. An affiliate is someone who recommends your product and earns a commission, a cut of each sale. Stripe is a payment service that many SaaS companies use to take card payments. SaaS means software people pay for each month or year.
In the Stripe pages we read, there is no built-in affiliate field. Stripe tracks payments, not people who referred them. So you carry an ID from the affiliate's link into Stripe, then read it back when the money lands.
This page shows that flow in plain words, then the Stripe fields and the limits behind it. Every Stripe fact comes from Stripe's own docs, checked on 10 November 2026. The design choices are our advice, not Stripe's rules.
How does a sale get tied to an affiliate?
Seven steps, from click to refund:
- A visitor clicks an affiliate's link. The link holds a short code for that affiliate.
- Your site saves the code. A cookie, a small note a site stores in a browser, is the usual place.
- The visitor signs up. You save the code next to the new account in your own database.
- At checkout, your server creates a Stripe Checkout Session. That is the payment page for one purchase. You put your own account ID in it.
- Stripe takes the payment. Then it sends a webhook, an automatic message from Stripe to your server, called
checkout.session.completed. - Your server reads the ID, finds the account, finds the affiliate saved at sign-up, and records a commission.
- If the customer is refunded or disputes the charge, Stripe sends another webhook. Your server cuts or cancels the commission.
Our advice is to pass your own account ID into Stripe, not the affiliate's code. The cookie can expire between sign-up and first payment. Your database still holds the code you saved at step 3, so the credit survives.
Which Stripe fields carry the ID?
Two fields do the job. The table gives Stripe's own limits.
| Field | What Stripe says it is for | Limit Stripe states |
|---|---|---|
client_reference_id on a Checkout Session | A unique string to reference the session, such as a customer ID or cart ID, to reconcile it with your own systems | 200 characters |
metadata on Stripe objects | Key-value data you attach to an object | Up to 50 keys, key names up to 40 characters, values up to 500 characters, no square brackets in keys |
Sources: the Create a Checkout Session reference and the Metadata reference, both checked 10 November 2026.
Stripe's metadata page lists Account, Charge, Customer, PaymentIntent, Refund, Subscription and Transfer among the objects that have it. It also says not to store sensitive details, such as card or bank numbers, there. Keep emails out of both fields too. An internal ID is enough.
One thing we could not confirm. The Checkout reference says subscription_data holds a subset of parameters passed to subscription creation, and payment_intent_data does the same for PaymentIntents. It doesn't say which metadata copies from the session to those objects. Test it in a Stripe sandbox before you rely on it.
The safer design is to treat your own database as the record. Save the Stripe customer ID next to the affiliate code when the first payment lands. Later renewals then find the affiliate by customer, whatever metadata did or didn't copy.
Which webhooks should credit a commission?
Stripe's fulfillment guide says you can't rely on your success page alone. A customer can pay, then lose their connection before the page loads. It calls webhooks required for subscriptions (Stripe, fulfill orders, checked 10 November 2026).
The table shows the events a SaaS usually needs. The last column is our advice.
| Event | What Stripe says | What to do |
|---|---|---|
checkout.session.completed | Created when someone pays through Checkout | Check payment_status first. Then record the commission |
checkout.session.async_payment_succeeded | Sent when a slower payment method, such as a bank debit, succeeds later | Record the commission now, if you waited |
invoice.paid | Sent when a subscription invoice is paid | Record a commission on each renewal, if your program pays on renewals |
invoice.payment_failed | A payment for an invoice failed | Don't credit. Wait for a paid invoice |
charge.refunded | Sent when a charge is refunded, in full or in part | Cut the commission by the same share. Use this event or refund.created, not both |
refund.created | Sent when a refund is created | Same refund seen from the refund side. Pick one trigger and key the cut on the refund ID, so one refund never cuts twice |
charge.dispute.created | A customer or their bank disputed a charge | Freeze the commission |
charge.dispute.funds_reinstated | Sent when funds come back after a dispute closes | Release the commission, if it was frozen |
Sources: Stripe, fulfill orders, Stripe, subscription webhooks and Stripe, refunds, all checked 10 November 2026.
For renewals, Stripe says to retrieve the subscription after invoice.paid and confirm its status is active before you extend access. Do the same check before you credit a renewal commission. That check is our reading of the guide, applied to commissions.
When you create a commission, save the PaymentIntent ID or invoice ID on it. A later refund event then finds its commission in one lookup.
What rules does Stripe set for webhook delivery?
Your handler has to follow these. They are the places where a quiet bug turns into a wrong payout.
- Verify the signature. Each event carries a
Stripe-Signatureheader. Use Stripe's libraries and the raw request body. Stripe's libraries default to a five-minute tolerance. - Reply fast. Return a 2xx status before any slow work. If you set a
success_url, Checkout waits up to 10 seconds for your reply before it redirects the customer. - Expect retries. In live mode, Stripe retries a failed delivery for up to three days. In a sandbox it retries three times over a few hours.
- Resend by hand if needed. The Dashboard resends an event for up to 15 days. The Stripe CLI can do it for up to 30 days.
- Expect disorder. Stripe doesn't guarantee events arrive in the order they happened. A renewal's
invoice.paidcan land before the subscription event. - Expect repeats. Log the event IDs you have processed. Skip any you have seen. Stripe says two separate events can also describe the same thing, so match on the object ID and event type.
Sources: Stripe, webhooks and Stripe, fulfill orders, checked 10 November 2026.
Make crediting safe to run twice. Stripe's guide says the same of fulfillment: it may be called many times, even at once, for the same session. A unique key on "one commission per payment" does this.
What happens to the commission on a refund?
Stripe allows more than one refund against a charge, and partial refunds. It won't let the total pass the original charge (Stripe, refunds, checked 10 November 2026).
So reverse in proportion. Here is a made-up example. A customer pays $50 and the affiliate earns 20%, which is $10. The customer gets $25 back. Cut the commission by half, to $5. A second $25 refund cuts the rest.
Two more cases from Stripe's refund page:
- A refund can fail, for example if the card was closed. Stripe then sends
refund.failedand says you must arrange another way to pay the customer. Check by hand before you restore any commission. - Stripe says refunds can take 5 to 10 business days to show on a customer's statement. Your commission cut should not wait for that.
The best protection is a hold. A hold is the number of days you wait before paying a commission, so a refund can still land first. Our guide to SaaS affiliate terms covers how to set it. A clawback is money you take back after you have already paid it. A hold covers refunds, not disputes.
What about disputes?
A dispute is when a customer asks their bank to reverse a payment. Stripe's guide to how disputes work (checked 10 November 2026) says it debits the disputed amount plus a dispute fee from your balance while the dispute is open. It also says you can't refund outside the dispute process meanwhile.
Stripe says card networks typically allow a dispute within 120 days of payment, with more time in some cases. The full process usually takes two to three months.
That gap matters for payouts. A hold of 30 days ends long before a dispute window closes. So write your own rule down. Our suggestion is to freeze the commission on charge.dispute.created. If you already paid it, decide in your terms whether you take it back from the next payout.
If you're also weighing which clicks should earn credit, read how long an affiliate cookie should last.
What goes wrong most often?
- Crediting from the success page only. Stripe says webhooks are the reliable route. Use both, with one safe function behind them.
- Crediting before the money is real. Check
payment_status. Slower methods, such as bank debits, can finish later. - Skipping the repeat check. Retries are normal. Without a log of event IDs, one sale pays twice.
- Keeping the code only in a cookie. Save it at sign-up, as in step 3.
- Missing self-referrals. Compare the buyer with the affiliate before you pay. The same email, or the same account, is a flag. This is our advice, not a Stripe feature.
- Never testing the unhappy paths. A sale that works is half the job.
How do you test it before real money moves?
Stripe's guides use a sandbox, the Stripe CLI and a test card. Run these six checks:
- Click a test affiliate link, sign up and pay with the test card. Check the commission.
- Replay the same event. Check no second commission appears.
- Trigger a renewal. Check one more commission, or none if your program pays once.
- Refund half. Check the commission halves.
- Refund the rest. Check it reaches zero.
- Open a test dispute. Check the commission freezes.
The CLI can forward events to your local server with stripe listen. It can also send test events with stripe trigger (Stripe, webhooks, checked 10 November 2026).
Build it or buy a tool?
The flow above is small to read and fiddly to get right. Most of the work is in the edge cases: repeats, partial refunds, holds and payouts. That is why many SaaS teams buy a tool. Our buyer checklist for SaaS affiliate software lists the questions to ask, and shows which billing tools each vendor lists.
We haven't tested how other vendors wire up Stripe, so we don't describe it here. Ask each one three things. Where does the ID go? Which events does it listen to? What happens to a commission after a refund that arrives post-payout?
AffiliateRail is our tracking tool, and it connects to Stripe or Paddle (AffiliateRail pricing). Tracking only works once you have affiliates. Affiliate marketing for SaaS covers the whole program. RecruitAffiliates.ai finds and emails the people who already cover products like yours. It doesn't track sales or pay anyone.
Frequently asked questions
Does Stripe have a built-in affiliate feature?
Not in the pages we read. Stripe documents payments, subscriptions, refunds and disputes. You link a sale to an affiliate with your own ID and your own records.
What is client_reference_id for?
Stripe describes it as a unique string, up to 200 characters, that reconciles a Checkout Session with your own systems. Put your account ID in it, not an email.
Should I use client_reference_id or metadata?
Use client_reference_id for the account ID on the session. Use metadata for extra notes, within its limits of 50 keys, 40-character key names and 500-character values. Keep the real record in your own database.
Which webhook should credit the commission?
For a first payment, checkout.session.completed, after you check payment_status. For renewals, invoice.paid, after you confirm the subscription is active.
How do I stop a refunded sale from paying out?
Hold the commission until your refund window ends. Cut it on charge.refunded in proportion to the refund, once per refund ID. Freeze it on charge.dispute.created. Card disputes usually come within 120 days, sometimes later, so a short hold can't cover them.
Sources
- Stripe, Create a Checkout Session, API reference, checked 10 November 2026
- Stripe, Metadata, API reference, checked 10 November 2026
- Stripe, Fulfill orders, checked 10 November 2026
- Stripe, Using webhooks with subscriptions, checked 10 November 2026
- Stripe, Receive Stripe events in your webhook endpoint, checked 10 November 2026
- Stripe, Refunds, checked 10 November 2026
- Stripe, How disputes work, checked 10 November 2026
- AffiliateRail pricing, checked 10 November 2026