In brief
No. Stripe has no affiliate feature at all. There is nothing for it in the API, and no partner screen in the Dashboard. What you get instead are four pieces you can build tracking out of. A reference field on the checkout holding up to 200 characters. Metadata on the objects a checkout creates. Promotion codes on the payment. And three webhook events. AffiliateRail's whole tracking script is one tag in the head of your pages. Following the click is the cheap part. The ledger, the holds, the clawbacks, the payouts and the tax forms are everything else.
No card. Cancel in one click.
Does Stripe have an affiliate program feature?
In shortNo. Stripe has nothing for affiliates in its API. There is no partner screen in the Dashboard. Nothing in its documentation pays a commission. Its App Marketplace lists other people's apps that do it. That is Stripe pointing at somebody else rather than building it.
Worth being blunt about, because it is the question behind the question. Search Stripe's own reference for a partner, an affiliate or a commission. Nothing comes back. No guide, no screen, no setting that switches one on. Stripe bills your customers, handles disputes, works out tax and moves money. Running an affiliate program is not on its list.
What Stripe gives you instead is better than a half-built feature would have been. Almost nobody bothers to explain it. That is why founders keep asking a question the docs technically answer. Four small things. Between them they carry a referral from a click to a commission. And back again through a refund.
So the honest answer is no. The useful answer is that Stripe has the parts. Here's each one, and what it costs to put them together.
The four parts Stripe does give you
In shortA reference field on the checkout. Metadata on the objects a checkout creates. Promotion codes on the payment. And three webhook events. That is the whole toolkit. Every affiliate tool for Stripe is built on it.
| The part | What it is | What it does for a program |
|---|---|---|
client_reference_id | A string on a Checkout Session, up to 200 characters | Carries the referral through the payment. Comes back on the webhook |
metadata | Key and value pairs on the session, and separately on the subscription or payment intent it creates | The server-side path, for a checkout you build yourself |
| Promotion codes | A promotion code is the discount code your customer types at checkout, recorded on the payment | Credits a partner whose audience never clicked a link |
| Three events | checkout.session.completed, invoice.paid, charge.refunded | Tie the customer to a partner, book the money, take it back |
The reference field carries the weight. client_reference_id is a string you put on a Checkout Session. Stripe hands it straight back to you on the webhook. Stripe defines it as a unique string for the session. It "can be a customer ID, a cart ID, or similar". The limit is 200 characters.
A unique string to reference the Checkout Session. This can be a customer ID, a cart ID, or similar, and can be used to reconcile the session with your internal systems. The maximum length is 200 characters.
Read that again with a program in mind. A click id is a unique string. It fits in 200 characters with room to spare. It comes back to you on the event that fires when the payment succeeds. Affiliate tracking on Stripe is that one sentence. It is used for something Stripe never had in mind.
There is a trap in that second row. A checkout has its own notes field. The subscription it creates has a different one. They are not the same field. So something you wrote on the checkout is not on the subscription afterwards. Write the referral into both. Or read it only from the checkout and never from a renewal.
How does the referral get onto the checkout?
In shortEither a script writes it on the page before the buyer clicks through. Or your server sets it when the session is made. The script path needs no code from you. The server path is three lines. It's the one to use if you build your own checkout.
A Checkout Session is the object Stripe creates for one attempt to pay. It's where the reference has to be by the time the buyer clicks through. The no-code path is the interesting one. It has to survive whatever your page happens to be made of. AffiliateRail's tracker stamps the click id onto three Stripe surfaces. A stripe-pricing-table element. A stripe-buy-button element. And any link to buy.stripe.com, where it rides along as a query parameter.
Two details in there separate tracking that works from tracking that mostly works.
The first is timing. A pricing table that draws itself after the page has loaded, for instance. A button inside a modal. A plan picker that swaps its links when the buyer flips monthly to yearly. All of those arrive after a script has finished its first pass. AffiliateRail keeps watching the page. It marks each one as it appears, however late that is. The rival scripts I captured for this repo use a timer instead. One gives up after ten tries at half a second each. Five seconds, then nothing.
The second is restraint. Let's say the merchant has already set their own reference on that button. They did it to match the sale against their own records. An affiliate tool has no business overwriting it. So AffiliateRail doesn't. One line of code, and it saves somebody's books.
Two warnings from Stripe's own page on Payment Link parameters. A reference can hold letters, digits, dashes and _, up to 200 characters. Anything invalid is "silently dropped" while the payment page carries on working. So a broken value never announces itself. It stops crediting anyone and says nothing at all. Stripe also warns that a link carrying the parameter "might show up in unexpected places". So the value must hold nothing secret. A click id is safe. A customer email is not.
Three events, and the one everybody wires first
In shortThree of them. checkout.session.completed ties the customer to the partner. invoice.paid is the one that actually books money. It fires again on every renewal. charge.refunded takes the commission back.
A webhook is Stripe calling your server when something happens, rather than your server asking. Most people wire the first one and stop there. Let's say a customer signs up in January through Jane's link. The session completes, Jane gets credited, everyone's happy. In February the invoice arrives with no session attached. It has no memory of the checkout that started the subscription. So nobody is credited at all. Recurring commission lives on invoice.paid. That is why the partner has to be stored against the customer and not against the payment. The recurring commissions page says which platforms pay on every renewal.
The refund event is where a program earns or loses trust. There are two cases. A commission you have not sent yet gets cancelled. One you already sent turns into a negative amount. It is taken off that partner's next payout. AffiliateRail matches the refunded charge back to its invoice, then does one or the other. And a customer only counts as refunded once none of their sales are still live. One refund among several payments is not a lost customer.
Cancellations matter too, and they're quieter. A subscription that ends stops sending invoices. So there's nothing to undo. What you want is the status on the customer. Then a partner's page shows who's still paying rather than who once did.
There is a fourth route worth knowing. It needs no click at all. A discount code belonging to a partner credits the sale by itself. The coupon page covers how that is set up. It also covers who wins when a buyer turns up with a code and a click.
Why does Stripe's connect screen ask for write access?
In shortBecause Stripe only offers read-only permission to one narrow category of app. Anything registered the usual way has to ask for read and write. That holds whatever it actually intends to do. So judge the tool by the key it uses, not by the permission screen.
This surprises people. It is better said out loud than left sitting on a scary screen. Stripe's OAuth reference says read-only "can only be specified for extensions". The page it links to is plainer still: "Only Extensions can use read_only, which ensures that platforms can't read other applications' data." So a tool has to ask Stripe to change how it is registered. Nothing on either page says the option was withdrawn, though. I have seen that claim made in several places. That includes AffiliateRail's own docs, which are being corrected.
So a tool connected to your Stripe was almost certainly granted write access. The promise that it never writes has to rest somewhere else. It rests on the key. A restricted key is a Stripe credential whose permissions you choose when you make it. AffiliateRail points one at a merchant's account. It can read charges, payment intents and invoices. It can write nothing at all. Stripe enforces that promise. Not good intentions.
Stripe agrees, in the plainest words it uses anywhere. Its keys page says a restricted key exists to "Limit the damage to your business that a fraudulent actor could cause if they obtained your key". Of the unrestricted kind it says this: "Because you can't limit their permissions, we don't recommend using secret keys for new use cases."
Two questions to ask any tool you are about to connect. Which key touches my account? What can that key do? Any honest answer is short. Maybe the answer is a shrug. Or a paragraph about how much they value your trust. Then you've learned what you needed to know. One question.
Run the affiliate program Stripe leaves you to build
One script tag, a key that can only read, and the ledger and payouts behind it. Fourteen days, no card.
Build or buy, and where the work actually is
In shortThe tracking, yes, in a weekend. A few kilobytes of script and one webhook handler. Tracking isn't the hard part. That's the thing worth knowing before you decide.
Here are the numbers, and they are not mine. I captured two rival trackers for this repo. One weighs 21,235 bytes. The other weighs 4,256. The bigger one is roughly five times the smaller, and both do the same job. Ours sits between them. So nobody in this category is winning on attribution, and a smaller file is not a reason to pick anyone.
What the script costs you is one line in the head of your pages. It loads on its own, so it never holds the page up. Then tracking is done, and the real work starts.
The rest is where the time goes. Start with the ledger. That is the running record of what every partner has earned and why. Every commission needs one reason and one amount. Let's say a partner asks why theirs was twelve dollars and not nineteen. They should be able to read the answer, rather than wait while you go digging. Then a hold, so nothing is payable before your refund window closes. A clawback is money you take back from a partner after paying it. Those have to survive a refund landing the week after a payout. Payouts grouped by partner, by currency, by term. Tax forms in first. A portal your partners trust enough to stop emailing you. Fraud rules that know a popular coupon from an abused one.
None of it is Stripe's job. None of it is in those four fields. That's the honest shape of the build-or-buy call. Attribution is the cheap part you could write yourself in a weekend. Everything above it is the reason almost nobody does.
To see it rather than read about it, the Stripe integration page has the script tag and the server-side snippet. It has the detail field by field. It behaves the same in test mode. The event shapes are identical there too. Or have your AI assistant add the tag and check it through the MCP server.
Common questions
Where in Stripe would an affiliate feature be, if it existed?
Nowhere, and that's checkable. There's no affiliate, partner or commission object in the API. There is no such screen in the Dashboard, and no guide in the docs. The App Marketplace lists apps that do the job instead.
Does this work with Payment Links, Pricing Tables and Buy Buttons?
All three, and they're why the no-code path exists. A link to buy.stripe.com takes the reference as a URL parameter. The two elements take it as an attribute. The catch is timing. Anything that renders late has to be stamped when it appears.
What happens to the commission when a customer refunds?
It follows the money. A commission not yet paid out is voided. One already sent comes off the partner's next payout. The customer is only marked refunded once none of their payments are live.
Do I need a Stripe API key of my own to run a program?
Not for the tracking. The connection runs through Stripe's own consent screen. The key that reads your account belongs to the tool. Ask which key it is and what it can do.
Is a webhook enough on its own, with no script on the site?
For the server-side path, yes. If you create your own Checkout Sessions, set the referral in metadata. Then you never load a script. The script exists for the no-code surfaces.
Can I test all of this without taking real money?
Test mode behaves the same. That is the point of proving it there first. The event shapes match. So a test click, a test card and a test refund walk the same path a live sale will.
Where these facts come from
Fact-checked and reviewed by Jimi Barkway on 4 September 2026. Every figure above was read off the document named here on the date beside it. To contact AffiliateRail about one, email support@affiliaterail.com and the figure is corrected and the date moved.
- Create a Checkout Session (Stripe API reference): client_reference_id and its 200-character limit, metadata, subscription_data and payment_intent_datachecked 4 September 2026
- The Checkout Session object (Stripe API reference)checked 4 September 2026
- Payment Links URL parameters (Stripe): the reference as a query parameter, the character rules, and the warning that invalid values are silently droppedchecked 4 September 2026
- Types of events (Stripe API reference)checked 4 September 2026
- The Promotion Code object (Stripe API reference)checked 4 September 2026
- API keys (Stripe): what a restricted key is for, and the recommendation against unrestricted secret keyschecked 4 September 2026
- OAuth reference (Stripe Connect): read_only can only be specified for extensionschecked 4 September 2026
- OAuth changes for platform-controlled Standard accounts (Stripe Connect): only extensions can use read_onlychecked 4 September 2026
- The Stripe App Marketplace: third-party apps rather than a built-in featurechecked 4 September 2026
- AffiliateRail docs: Attribution and tracking, the four paths and the attribution-health panelchecked 4 September 2026