RecruitAffiliates.ai, home Start free

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:

  1. A visitor clicks an affiliate's link. The link holds a short code for that affiliate.
  2. Your site saves the code. A cookie, a small note a site stores in a browser, is the usual place.
  3. The visitor signs up. You save the code next to the new account in your own database.
  4. 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.
  5. Stripe takes the payment. Then it sends a webhook, an automatic message from Stripe to your server, called checkout.session.completed.
  6. Your server reads the ID, finds the account, finds the affiliate saved at sign-up, and records a commission.
  7. 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.

FieldWhat Stripe says it is forLimit Stripe states
client_reference_id on a Checkout SessionA unique string to reference the session, such as a customer ID or cart ID, to reconcile it with your own systems200 characters
metadata on Stripe objectsKey-value data you attach to an objectUp 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.

EventWhat Stripe saysWhat to do
checkout.session.completedCreated when someone pays through CheckoutCheck payment_status first. Then record the commission
checkout.session.async_payment_succeededSent when a slower payment method, such as a bank debit, succeeds laterRecord the commission now, if you waited
invoice.paidSent when a subscription invoice is paidRecord a commission on each renewal, if your program pays on renewals
invoice.payment_failedA payment for an invoice failedDon't credit. Wait for a paid invoice
charge.refundedSent when a charge is refunded, in full or in partCut the commission by the same share. Use this event or refund.created, not both
refund.createdSent when a refund is createdSame 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.createdA customer or their bank disputed a chargeFreeze the commission
charge.dispute.funds_reinstatedSent when funds come back after a dispute closesRelease 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-Signature header. 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.paid can 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.failed and 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:

  1. Click a test affiliate link, sign up and pay with the test card. Check the commission.
  2. Replay the same event. Check no second commission appears.
  3. Trigger a renewal. Check one more commission, or none if your program pays once.
  4. Refund half. Check the commission halves.
  5. Refund the rest. Check it reaches zero.
  6. 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

Keep reading

Build your affiliate army.

Start free and see who's out there today.