How to Set Up an Affiliate or Referral Program With Paddle
Set up an affiliate or referral program on Paddle Billing: pass the partner in custom data, commission on transaction.completed, and reverse refunds.
Muzahid Maruf, Founder
On this page
Explore with AI
Opens this article inside the chosen assistant with a ready-made prompt.
To set up an affiliate program with Paddle, store the partner's ID at the click, pass it into Paddle Checkout as custom data, and write a commission when a signed transaction.completed webhook arrives.
Where Stripe Checkout takes a client_reference_id, Paddle Checkout takes customData. Paddle is also the merchant of record, so its totals include tax and a refund never edits the original transaction.
Key takeaways
- Commission on transaction.completed, and on the pre-tax subtotal: in Paddle's own API example, 20% of the $599.00 subtotal is $119.80, against $130.43 on the $652.15 total.
- Refunds arrive as adjustments, and Paddle Billing can approve live refunds of 400 USD or less automatically, so reverse commissions on adjustment events.
- Use Paddle Billing, which accounts opened since August 8, 2023 get. Rewardful's own page says it supports Paddle Classic only.
How Paddle affiliate tracking works
Paddle Billing opens checkout from a page on your approved domain that loads Paddle.js. Your script there can read the first-party cookie, and Paddle's overlay or inline form never sees it, so the partner ID goes into customData in Paddle.Checkout.open().
Paddle Billing then copies it to the subscription and every renewal. Its fee is 5% + 50¢ per checkout transaction.
| Stage | What happens | Paddle field or event |
|---|---|---|
| 1. Click | Your script stores the partner's ID in a cookie | None |
| 2. Checkout | Your page opens Paddle Checkout with the ID attached | customData |
| 3. Payment | Paddle charges the buyer and creates the subscription | transaction.paid, then transaction.completed |
| 4. Commission | Your server verifies the webhook and writes a commission | Paddle-Signature, custom_data |
| 5. Renewal | Paddle copies the custom data onto each renewal | transaction.completed again |
| 6. Refund | Paddle records an adjustment and leaves the transaction alone | adjustment.created, adjustment.updated |
How to set up an affiliate program with Paddle, step by step
Do steps 1 to 7 in a Paddle sandbox, which has its own data and charges no real cards, and step 8 on your live account.
Step 1: Check your Paddle version and open a sandbox
Per Paddle's migration guide, accounts opened before August 8, 2023 start on Paddle Classic and later ones on Paddle Billing, with separate data, APIs and webhooks. This post covers Billing.
Classic passes data in a passthrough string of up to 1,000 characters (checkout parameters) and added structured custom data in August 2022.
Open a Paddle Billing sandbox at sandbox-vendors.paddle.com, where API keys contain _sdbx and client-side tokens start with test_.
Step 2: Set the commission rules
| Rule | Example setting | Paddle detail |
|---|---|---|
| Rate and term | 25% for 6 months | Each renewal is its own transaction.completed (how to pick a rate) |
| Commission base | Subtotal minus discount | Totals include tax (step 6) |
| Attribution window | 30 to 90 days | Safari clears script cookies after 7 idle days (step 3) |
| Hold period | 30 days, your refund window | A refund can be approved after Paddle's review |
| Payout | Monthly, $100 minimum | Paddle pays you, so paying partners is a separate run |
On a $29 monthly plan, 25% recurring for 6 months pays $7.25 per renewal and $43.50 per customer who stays the whole term (29 x 0.25 x 6). A customer who cancels after 2 payments costs $14.50.
Step 3: Save the partner ID at the click
A script writes the partner's ID into a first-party cookie on arrival, ready for step 4.
Safari's Intelligent Tracking Prevention deletes cookies created in JavaScript after 7 days without interaction, so a Monday click and a purchase 10 days later, with no visit in between, reach checkout with no ID.
Keep a second match key, such as an email captured at signup or a coupon code (step 6).
Step 4: Pass the partner ID into Paddle Checkout
Paddle Billing's custom data guide asks for a JSON object with at least one key and advises against nesting.
// "ref" is the first-party cookie your affiliate tracking wrote at the clickconst partner = document.cookie.match(/(?:^|; )ref=([^;]+)/)?.[1] ?? null; Paddle.Checkout.open({ items: [{ priceId: "pri_01gs59hve0hrz6nyybj56z04eq", quantity: 1 }], customData: { partner_id: partner },});A server-created transaction takes the same object as custom_data in POST /transactions, before billing. Paddle Billing copies a transaction's custom data to its subscription, and a subscription's to every later transaction, so one ID covers every renewal.
The Paddle checkout attribution guide goes deeper.
Step 5: Receive transaction.completed webhooks
In the Paddle dashboard, open Events, then Notifications, and subscribe a webhook destination to transaction.completed, adjustment.created and adjustment.updated. Commission on completed: a paid transaction may still lack payout totals, a subscription ID and an invoice number.
| Webhook rule | Paddle's value |
|---|---|
| Signature | The Paddle-Signature header holds ts and h1, an HMAC SHA-256 of ts, a colon and the raw body |
| Replay check | Paddle's SDKs reject a ts more than 5 seconds old by default |
| Response deadline | 200 within 5 seconds, then process from a queue |
| Live retries | Up to 60 over 3 days, 20 of them in the first hour and 47 in the first day |
| Sandbox retries | 3 over 15 minutes |
| Allowlist | 6 live and 6 sandbox source IP addresses, listed in Paddle's delivery docs |
| Active destinations | 10 at once, each with its own pdl_ntfset_ secret |
| Deduplication | event_id, prefixed evt_ |
| Ordering | Not guaranteed, so compare occurred_at |
From Paddle's signature verification, delivery and notification destination docs.
import { createHmac, timingSafeEqual } from "node:crypto"; export async function POST(req: Request) { const raw = await req.text(); // the raw body, exactly as Paddle sent it const header = req.headers.get("paddle-signature") ?? ""; const ts = /ts=(\d+)/.exec(header)?.[1]; const sigs = [...header.matchAll(/h1=([0-9a-f]+)/g)].map((m) => m[1]); if (!ts || sigs.length === 0) return new Response("bad request", { status: 400 }); const expected = createHmac("sha256", process.env.PADDLE_WEBHOOK_SECRET!) .update(ts + ":" + raw) .digest("hex"); const valid = sigs.some( (s) => s.length === expected.length && timingSafeEqual(Buffer.from(s), Buffer.from(expected)), ); if (!valid) return new Response("invalid signature", { status: 401 }); // 300 seconds is wider than the SDK default of 5, to tolerate clock skew if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return new Response("stale", { status: 400 }); const event = JSON.parse(raw); if (await alreadySeen(event.event_id)) return new Response("ok"); // your dedupe store await enqueue(event); // your queue; commission logic runs after the 200 return new Response("ok");}Step 6: Calculate and record the commission
In the verified event from step 5, read data.custom_data for the partner, data.subscription_id for the subscription and data.details.totals for the money.
Paddle Billing sends amounts as strings in the currency's smallest unit, so the "59900" subtotal in its API reference example is $599.00. Store currency_code on every commission row.
That example has $53.15 of tax at 8.875%, a $652.15 total, a $33.11 fee and $565.89 of earnings.
| Base | Paddle field | Amount | 20% commission |
|---|---|---|---|
| What the buyer paid | details.totals.total | $652.15 | $130.43 |
| Price before tax | details.totals.subtotal | $599.00 | $119.80 |
| Price before tax and fee | details.totals.earnings | $565.89 | $113.18 |
Amounts from the example transaction in Paddle's API reference. The 20% rate is only for the arithmetic.
In that example the $33.11 fee is 5% of the $652.15 total plus 50¢, so Paddle charged it on the tax-inclusive amount.
The earnings field is described as the total minus the fee, yet the example shows the subtotal minus the fee ($599.00 minus $33.11), so check a sandbox payload in step 8 before relying on it.
Use the subtotal minus any discount, the base from step 2, which partners can verify against your price list.
On a $29 plan priced before 20% VAT the total is $34.80, so 25% of the total pays $8.70 against $7.25, which is $1.45 too much on every renewal.
Each renewal from step 5 is its own transaction.completed with the same custom data, so write one commission per transaction up to your term.
To credit partners who send no clicks, give each a Paddle discount code and match discount_id; a discount covers only the first transaction unless it is recurring.
Step 7: Reverse commissions on refunds and chargebacks
Paddle Billing's adjustments guide says billed and completed transactions can't change, so a refund is an adjustment and the transaction stays completed, which a tool reading only transaction status misses.
Most live refunds start as pending_approval and become approved or rejected; Paddle approves a live refund itself at 400 USD or less, if the account is verified, the amount is under your balance and the buyer didn't pay by bank transfer.
Reverse the step 6 commission once an adjustment is approved. A partial refund takes back the same share: $10 refunded on a $40 sale at 20% returns $2 of the $8 commission.
Chargebacks arrive as adjustments with the actions chargeback_warning, chargeback and chargeback_reverse; reverse on the first two and restore on the third. The refund clawback guide covers commissions already paid.
Step 8: Test in the sandbox, then go live
Paddle Billing's webhook simulator can replay a renewal, but only to destinations whose usage type is Platform and simulation or Simulation.
Buy through a partner link, simulate a renewal, refund the payment (the sandbox approves refunds every 10 minutes) and check the commissions from steps 6 and 7.
To go live, swap in live credentials and recreate the catalog and notification destination, as the go-live checklist lists.
Setting up a referral program on Paddle
A referral program rewards customers for bringing in new ones, and steps 3 to 8 carry over: the referrer's ID travels in custom data and the reward fires on the friend's first transaction.completed.
Paddle Billing's credits aren't promotional credits, so pay cash or put a Paddle discount on the referrer's subscription, which can recur for up to 999 billing periods.
A discount's usage limit is one total across all customers, so a one-use-per-friend rule is yours to write. TrackRev's referral campaigns add cash or credit rewards on paid plans.
Tool options for a Paddle affiliate program
Prices are in the comparison of affiliate software for Paddle. Ask every vendor which total it commissions on and whether it reads adjustment events.
| Option | What its page says about Paddle |
|---|---|
| Build it yourself | Steps 3 to 7, plus a partner portal, payout run and tax forms |
| Rewardful | Integrates with Paddle Classic only, and not with Paddle Billing |
| Tolt | Two-way sync, with commission deducted on issued refunds; no version named |
| Affonso | Added in 2025, covering refunds, discount codes and referral links; no version named |
| TrackRev | Paddle Billing API key, hourly sync and an optional instant webhook; no Paddle Classic |
Paddle support as each vendor describes it, October 2026.
What TrackRev does for a Paddle program
| Setting | Value |
|---|---|
| Plans | Starter $39 a month, Growth $99 and Scale $199, capped at $10K, $100K and unlimited commission tracked monthly; annual billing pays for 9 months and gives 12; the free plan has 50 links and 1,000 events a month, for link tracking only |
| Connection | Read-only Paddle Billing key with Customer, Product, Subscription, Transaction and Discount access |
| Sync | Hourly, 30 days back on the first run, instant with an optional webhook on transaction.completed |
| Join key | customData: { vid: window.trk.vid } in the step 4 call, falling back to the buyer's email |
| Renewals and codes | Renewals inherit the first visitor; a Paddle discount code assigned to a partner credits them without a click |
| Cookie window | 60 days by default, 1 to 3,650 allowed |
| Hold period | 0 to 180 days, default 0 |
| Payouts | PayPal or Wise CSV export, then mark each paid |
TrackRev's Paddle integration verifies the webhook signature, then reads the transaction from the Paddle API. Choose another route on Paddle Classic, or for a managed marketplace of partners, which PartnerStack vs TrackRev compares. Pricing lists the plans.
Found this useful? Share it.
Frequently asked questions
- On Paddle Billing, attach the partner's ID as custom data in the checkout call or on a server-created transaction. The webhook returns it, renewals included.
- transaction.completed. Unlike transaction.paid, it arrives after Paddle Billing adds the fee, earnings and subscription ID.
- On the subtotal minus any discount, which is $599.00 in Paddle's example against a $652.15 total and $565.89 of earnings.
- On Paddle Billing the transaction stays completed and an adjustment records the refund. Reverse the commission when it is approved; live refunds of 400 USD or less can approve automatically.
- No. Its connect form asks for a Paddle Billing API key and warns against a Classic one.
- No. The free plan covers link tracking, with 50 links and 1,000 tracked events a month. The affiliate program starts on the $39 Starter plan.

Written by
Founder, TrackRev.io & Contant.io
Muzahid Maruf is the founder of TrackRev.io and Contant.io. He writes about marketing attribution, link tracking, and revenue analytics for SaaS teams.
Writes about Marketing attribution · Link tracking · Revenue analytics · SaaS growth
Stop guessing where your revenue comes from.
Set up TrackRev in about five minutes. The free plan covers 1,000 events a month, no card needed.
Start free