Dev.to Security 🔐 Cybersecurity 👁 0 📖 2 min read

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

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));
}

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 });
  }
}

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.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.