A referral program needs to know three things: who arrived through a partner's link, who signed up, and who paid (or asked for their money back). The link and the tag on your site answer the first. Your billing system answers the last, and Bolt Claw connects to twelve of them directly, so most programs need no code on the server at all.
Connect yours from the dashboard: open the program, Setup, Billing, pick the system you take payments with. Each card says exactly what to paste where. Connecting one is done in the dashboard and never through the API or an AI assistant, because every integration needs a credential pasted or shown once, and a secret must never pass through a chat with a model.
Then watch the setup status at the top of the same tab: one row per part, each
Connected, Connected with warnings or Not connected, with the exact fix.
When it is all green, press Send a test sale to see the whole path work
without real money.
Every billing system, at a glance
| Billing system | How a delivery proves itself | Codes typed at checkout | Live or test | Disputes | Partial refunds | Guide |
|---|---|---|---|---|---|---|
| Stripe | Signing secret, or the Bolt Claw Stripe App | Learned from promotion code events | Each event says | Yes | Yes | Stripe |
| Paddle Billing | Signing secret, timestamped | Learned from discount events | Sandbox switch | Yes | Yes | Paddle |
| Polar | Standard Webhooks | Arrive as text | Sandbox switch | No | Yes | Polar |
| Whop | Standard Webhooks | Arrive as text | Sandbox switch | Yes | Yes | Whop |
| RevenueCat | A header token Bolt Claw makes, plus an optional signature | Offer codes arrive as text | Each event says | No | No | RevenueCat |
| WooCommerce | Signing secret, no timestamp | Arrive as text | Sandbox switch | No | Yes | WooCommerce |
| FastSpring | Signing secret, no timestamp | Arrive as text | Each event says | Yes | Yes | FastSpring |
| Lemon Squeezy | A signing secret Bolt Claw makes | Not sent at all | Each event says | No | Yes | Lemon Squeezy |
| Shopify | The store's signing key, no timestamp | Arrive as text | Each order says | Yes | Yes | Shopify |
| Chargebee | Basic auth Bolt Claw makes (no body signature) | Not read | Sandbox switch | No | Yes | Chargebee |
| Recurly | Signing secret, plus a read-only key | Not read | Sandbox switch | No | Yes | Recurly |
| Gumroad | None: every sale is re-read from Gumroad | Offer codes arrive as text | Each sale says | Yes | No | Gumroad |
Every integration has its own endpoint URL, shown on its card, and it answers nothing until it is set up. What a delivery gets back:
| Status | When |
|---|---|
200 |
Every delivery that verified, whether it counted or not (a refund of a sale this program never saw, a status Bolt Claw does not count). Sending it again will not change the answer. |
400 |
A bad or stale signature, or a body that cannot be read. |
401 |
The wrong credentials, for the two systems where Bolt Claw made them (Chargebee, RevenueCat). |
404 |
No such integration, or one that is switched off or not set up yet. |
413 |
A body over 1 MiB. |
500 |
Our fault, or the billing system's own API was unreachable: it retries, and the retry is read. |
Nothing counts twice. A billing system that signs no timestamp (WooCommerce, FastSpring, Lemon Squeezy, Shopify, Chargebee, Gumroad) could have a delivery replayed, so each delivery id is claimed before the body is read, and each sale, refund and dispute has a stable id of its own: a replay, a retry and a redelivery all write nothing new. Each billing system is its own source, so its event ids never collide with yours or with another system's.
The customer. Every system can carry your own id for the customer (the
field boltclaw_customer_id, renamable on the card), so a sale is matched to a
signup you reported. Without it, the billing system's own customer id stands
in, and its renewals still find the customer.
Not on any of these
Report sales from your own server with POST /v1/referrals/conversions:
a key with the Referral tracking preset (conversions:write alone), your own
event_id for each sale, signup, refund and dispute, and the code the tag kept.
The setup status counts a sale that arrived this way as a connected billing
system.
Stripe: one click, or a webhook
The Bolt Claw Stripe App connects Stripe with no webhook to create and no secret to paste: Connect Stripe sends you to Stripe, you approve the app on your account, and you come back connected. It asks only for what it uses: reading events, Checkout Sessions, invoices, charges and promotion codes, and writing coupons, promotion codes, customers and subscriptions (for the friend's discount codes, account credit and free months). It holds no payout permission. One Stripe account can be connected to one program at a time. Uninstalling the app in Stripe disconnects it here; disconnecting it here uninstalls it in Stripe too. Every conversion it recorded stays.
The webhook is the way that needs no app: add an endpoint in Stripe with the URL on the card, select the events it lists, and paste the endpoint's signing secret. Two things matter on Stripe's side:
- Choose Snapshot, not Thin. Stripe's dashboard now defaults new endpoints to Thin events, which carry only an id. Bolt Claw records that it received one, and the setup status says "Choose Snapshot".
- Select
invoice_payment.paid. On Stripe API versions from2025-03-31, an invoice no longer names the payment that paid it, so a refund of a subscription invoice could not find its sale.invoice_payment.paidlinks the two, with no key at all, and a refund is then matched whichever of the events arrives first. The setup status warns when it is missing.
Either way, a Stripe event says whether it is live or a test, and a test-mode event is recorded as test data.
Stripe Payment Links, with no code
If you sell through Stripe Payment Links or the Buy Button, the website tag is the whole integration:
<script src="https://boltclaw.ai/referrals/v2.js" defer data-payment-links="auto"></script>
It puts the partner's code on every Payment Link on the page as
client_reference_id (and the friend's discount code as
prefilled_promo_code, when the program gives the friend one), and on every
<stripe-buy-button> as client-reference-id. A value you already set is left
alone. Connect Stripe as above and paid checkouts are credited by that id.
The live setup status
At the top of the Setup tab, and at
GET /v1/referrals/programs/{id}/integration-status for the
CLI and an AI assistant. Every row is decided from what Bolt Claw has actually
seen for this program: live data only, because a test sale proves the path and
not your own wiring.
| Part | Connected when | Warnings |
|---|---|---|
| Website tag | The tag was seen on your product page | You have version 1 rather than 2; your audience includes the EU or the UK and the tag does not wait for consent |
| Billing | A verified event in the last 30 days, or a sale through the API | Deliveries refused since the last good one (usually the wrong secret); Stripe's Thin payloads; an old Stripe API version; invoice_payment.paid not selected |
| Signups | A signup (lead) arrived |
Signups named by an email address rather than your own id |
| Sales | A sale arrived | Sales in the last 7 days, none of them referred, while clicks arrive |
| Codes | A sale was credited by a discount code alone, or your billing system's code events arrived | Your billing system's codes match none of this program's codes |
| In-app panel | Your server minted a panel session and a panel was opened | No site is allowed to show the panel |
| Friend's welcome | The friend's offer was shown to a visitor |
A part your own setup does not use is marked as skipped and does not count: signups when people only sign up at checkout, the panel while it is switched off, codes while the program has none.
Seeing the website tag
Check again fetches your product page once, from our server, and looks for
/referrals/v2.js (or v1.js) in a script tag. The fetch is careful about
where it goes: https only, only your program's own hosts (the product URL's,
and the ones you allowed the welcome to read from), never a private address
(checked after the name is resolved, and again on every redirect, three at
most), five seconds and 512 KB at most, and a few checks per program an hour.
POST /v1/referrals/programs/{id}/integration-status/tag-check
does the same from the API.
A tag your site adds with JavaScript (a tag manager, a single-page app) is
invisible to a server-side fetch. For those, Open your site with this link
opens your product with ?boltclaw_verify=<nonce>: the tag sees that parameter
and makes one request to say it is there, carrying the nonce, its version and
whether consent mode is on, and nothing else. The link works once, for half an
hour. Without that parameter in the address, the tag never makes this request.
Test data
Test data is the real path with nothing real at the end of it. It comes from four places:
- Send a test sale on the Setup tab, or
POST /v1/referrals/programs/{id}/test-sale: a click on the program's test partner's link, a signup, a sale of 100.00 in the program's currency with two items, then a partial refund of 25.00. Each step shows what it caused as it happens: the commission and why, any reward that is not cash, the friend's offer, and the webhook deliveries it queued. test: trueonPOST /v1/referrals/conversions.- A test-mode event from a billing system whose events say so (Stripe, FastSpring, Lemon Squeezy, RevenueCat, Shopify, Gumroad).
- The Sandbox switch on an integration's card, for a sandbox account whose events do not say (Paddle, Polar, Whop, WooCommerce, Chargebee, Recurly): add a second integration for the sandbox and switch it on. Every event it receives is test data.
What test data never does:
- It is never paid. A test commission never reaches a payout, and the database refuses one in a payout or marked paid.
- It is never counted. Not in the program's stats, not in the click allowance, not in any total.
- It is credited to "Test partner" only, the program's own test partner, whoever's link or code it carried. No partner ever sees your tests. The test partner's link is archived from the start, so no visitor can open it.
- It emails nobody.
- It reaches a webhook only if the endpoint asked. Switch Also send test
events on for an endpoint (
test_eventsonPOST /v1/webhooks); a test delivery's body carries"test": true, and a live one never does. - It never collides with live data. A test
event_idand a live one with the same text are two different conversions, and a test customer and a live one with the same id are two different customers.
Conversions, commissions and rewards from test data carry test: true, and the
dashboard marks them Test. Clear test data (or
DELETE /v1/referrals/programs/{id}/test-data) deletes
every test row of the program and its test partner, and touches nothing live.
An AI assistant is always asked first.
Where a setup is weaker, and what to do
- Chargebee signs nothing: it can only protect its webhook with Basic auth. Bolt Claw makes a random username and password, keeps only the password's hash, and lets you make new ones at any time. Keep them private, and make new ones if they ever leak.
- Gumroad's ping is not signed, so nothing in it is ever counted: every sale it names is read from Gumroad with your access token, and only what Gumroad's API returns is recorded. A partial refund cannot be told from a full one in Gumroad's data, so report a partial refund through the API.
- Lemon Squeezy sends no discount code, so a sale made with a code alone cannot be credited: use links (or pass the referral at checkout).
- Recurly sends ids only, so each invoice is read through Recurly's API with a read-only key before it counts.
- RevenueCat's header token alone proves who sent a request but not what it said; turn on RevenueCat's signing and paste its secret too.
Quickstarts
By billing system: Stripe, Paddle, Polar, Whop, RevenueCat, WooCommerce, FastSpring, Lemon Squeezy, Shopify, Chargebee, Recurly and Gumroad.
By how your product is built: Next.js, Express, Supabase, Firebase, Clerk, Auth.js, Better Auth, Django, Rails, Laravel and a site with no code.
Moving from another tool: Moving from Rewardful, FirstPromoter, Tolt or Dub.
Working with an AI coding assistant: the dashboard's Copy a prompt for your AI
coding assistant hands it your program's id, your stack and the steps, and
points it at https://boltclaw.ai/docs/referrals/install.md, a page written for
coding agents. It never contains your key.