DEV Community

Mihir kanzariya
Mihir kanzariya

Posted on

Catching affiliate self-referrals before you pay out (Stripe)

Once your affiliate program pays real money, someone will refer themselves. They sign up through their own link, or route a friend they coach through it, collect the commission, and on a recurring product they can churn right after the first payout. If you credit commission the moment an invoice is paid, you just funded a discount to yourself.

The good news: Stripe already hands you most of the signals to catch this. You do not need a fraud vendor to start, you need to look at the data you are storing anyway.

The strongest signal: payment method fingerprint

Every card in Stripe has a fingerprint that stays stable across customers. If the affiliate's own subscription and the customer they referred were paid with the same physical card, that is not a coincidence.

async function sharesPaymentMethod(affiliateCustomerId, referredCustomerId) {
  const [a, b] = await Promise.all([
    stripe.paymentMethods.list({ customer: affiliateCustomerId, type: 'card', limit: 20 }),
    stripe.paymentMethods.list({ customer: referredCustomerId, type: 'card', limit: 20 }),
  ]);
  const aPrints = new Set(a.data.map(pm => pm.card.fingerprint));
  return b.data.some(pm => aPrints.has(pm.card.fingerprint));
}
Enter fullscreen mode Exit fullscreen mode

A match here is close to a smoking gun. Flag it, do not pay it.

Cheaper signals worth stacking

Fingerprint is the best one, but a few more catch the sloppier cases:

  • Email overlap. Same address, obvious plus-addressing (me+ref@), or the same domain on a personal-email product. Normalize before comparing.
  • Billing address match between the two customers.
  • Radar / IP. If you use Stripe Radar, the charge carries risk signals and an IP. Same IP for referrer and referred is worth a flag.
  • Churn inside the window. The referred subscription cancels or refunds right after the commission-eligible invoice. This is the classic grab-and-run.

None of these alone should auto-ban. Stacked, they sort the obvious abuse from the false positives.

Where to run the check

Do it at commission time, not signup. On invoice.paid, before you write the credit:

// case 'invoice.paid'
const affiliateId = invoice.customer_metadata?.affiliate_id;
if (affiliateId) {
  const affiliateCustomer = await lookupAffiliateCustomer(affiliateId);
  const selfReferral =
    affiliateCustomer &&
    await sharesPaymentMethod(affiliateCustomer.stripe_id, invoice.customer);

  if (selfReferral) {
    await ledger.flagForReview({ affiliate_id: affiliateId, invoice: invoice.id, reason: 'card_fingerprint_match' });
  } else {
    await ledger.credit({ affiliate_id: affiliateId, amount: commissionOn(invoice.amount_paid), idempotency_key: invoice.id });
  }
}
Enter fullscreen mode Exit fullscreen mode

The pragmatic policy that stops most of the loss: hold the commission until the referred subscription clears its refund window, and route fingerprint matches to a human instead of a payout. You lose nothing legitimate by waiting a few weeks, and you stop paying people to refer themselves.

We handle this inside Referralful (affiliate software built on Stripe, which I help build), so the fingerprint and churn checks run before a payout is ever approved. Even if you build your own, the payment method fingerprint is the single highest-value check to add first.

Top comments (1)

Collapse
 
vollos profile image
Pon

Fingerprint is the right first check — high precision, but low recall. It nails the lazy self-referrer paying with one card and misses anyone who drops a Privacy.com or Revolut virtual number on the referred account, which is a thirty-second move now. The harder-to-fake signal is downstream: a real referred customer uses the product, while a self-referral tends to be a dormant account that exists only to clear the commission window. You've got churn-inside-the-window for the grab-and-run; usage-inside-the-window catches the patient ones who let the sub ride. Either way, holding payout till the refund window clears is the policy that does the heavy lifting.