DEV Community

Mihir kanzariya
Mihir kanzariya

Posted on

Passing affiliate attribution through Stripe Checkout so recurring commissions still work

The hard part of affiliate tracking on Stripe isn't the first sale. It's making month 6's renewal invoice still know which affiliate to pay.

Here's the situation. An affiliate link sets ?ref=abc. Someone clicks it, pokes around, closes the tab, and subscribes three days later through Stripe Checkout. By the time invoice.payment_succeeded fires, and again every month after that, the browser is long gone. So where does the affiliate ID actually live?

Step 1: capture the ref on click and persist it

When a visitor lands with ?ref=abc, store it in a first-party cookie (match your cookie window, say 60 days) with localStorage as a backup:

const params = new URLSearchParams(window.location.search);
const ref = params.get("ref");
if (ref) {
  document.cookie = `affiliate_ref=${ref}; max-age=${60 * 24 * 60 * 60}; path=/; SameSite=Lax`;
  localStorage.setItem("affiliate_ref", ref);
}
Enter fullscreen mode Exit fullscreen mode

Step 2: attach the ref when you create the Checkout Session

This is where most implementations lose the recurring case. client_reference_id is handy, but it lives on the session, not on future invoices. For commissions that recur, put the ref on the subscription's metadata too:

const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [{ price: priceId, quantity: 1 }],
  client_reference_id: ref,              // attributes THIS checkout
  subscription_data: {
    metadata: { affiliate_ref: ref },    // rides along on every renewal invoice
  },
  success_url: "https://yourapp.com/welcome",
  cancel_url: "https://yourapp.com/pricing",
});
Enter fullscreen mode Exit fullscreen mode

Step 3: read it in the webhook, on the invoice, not just the session

Credit the commission from the recurring event so renewals attribute, not only the first payment:

// invoice.payment_succeeded
const invoice = event.data.object;
const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
const ref = subscription.metadata.affiliate_ref;

if (ref) {
  await creditCommission({
    affiliateRef: ref,
    invoiceId: invoice.id,               // idempotency key, see below
    amount: invoice.total_excluding_tax, // already post-discount, post-proration
  });
}
Enter fullscreen mode Exit fullscreen mode

The gotchas that bite later

  • client_reference_id lives on the Checkout Session only. If that's your sole source of truth, month 2 renews with no affiliate attached. Store the ref on the subscription (or customer) metadata so it survives.
  • Stripe metadata is a flat string map with capped keys and values. Store the affiliate's id, not a JSON blob of their whole profile.
  • A plan change can spin up a new subscription. Metadata doesn't copy itself forward, so carry it over on upgrade, or attribute at the customer level instead.
  • You will receive the same invoice webhook more than once. Key the commission insert on invoice.id (a unique index) so a retry can't double-pay.

The one-line version: attribute on the subscription, settle on the invoice. Do that and a referral you earned in January still pays out correctly in July.

I build Referralful, Stripe-native affiliate software for SaaS, so this is the exact plumbing it handles for you. The pattern above works the same whether you use us or roll your own.

Top comments (0)