Say you open the Stripe dashboard on a Tuesday morning and the home chart has a spike you didn't earn. Two hundred payment attempts overnight, almo...
For further actions, you may consider blocking this person and/or reporting abuse
The SetupIntent point is the one I'd underline. On a "card required" trial, I'd only create the SetupIntent after the email is confirmed, and cap it at one open SetupIntent per account and a couple of card attachments per day. A tester working through a list needs hundreds of attempts, so making each attempt cost a confirmed signup kills the economics fast, and real users never notice.
tying the SetupIntent to a confirmed email is a really nice move, a real user loses a few seconds and a tester has to burn an inbox per attempt. the one-open-SetupIntent cap is smart too. do you limit by account only, or also by ip or device? confirmed emails can get pretty cheap for someone running a list.
Account alone isn't enough, you're right. I'd count attempts per account, per IP and per card fingerprint (Stripe puts one on every card PaymentMethod), and treat a fingerprint that already showed up on another account as the strongest signal. IP is worth a soft limit but rotates cheaply, so I wouldn't hard block on it. Cheap confirmed emails tend to cluster on a few domains or on plus and dot variants of one inbox, so normalising the address before the check catches a fair share of them too.
the card fingerprint showing up on a second account is a really good signal, i hadn't thought of using it across accounts like that. normalising plus and dot variants before the check is a cheap win too. thanks for writing this out.
The detail most teams miss sits in the velocity section: hourly velocity excludes the payment being evaluated, so the first attempt from an IP is 0, not 1. A rule like block if total_charges_per_ip_address_hourly > 1 only bites on the third attempt, which is exactly why the paired blocked-charges rule earns its place. The 5-minute bucket note is the kind of thing that makes a rule behave differently in testing than in production. The strongest section is the one about Radar only seeing what reaches Stripe: a script hammering your endpoint that never gets authorized is invisible to every Radar model, so the login-plus-CAPTCHA gate on session creation is the actual defense and Radar is the safety net behind it. And not retrying fraudulent cards through dunning is a subtle catch; your retry engine would keep re-asking the attacker's card to pay, which is the attack continuing on your infrastructure.
the 0-not-1 thing on hourly velocity is exactly the kind of detail that looks fine in testing and then quietly lets the first two attempts through in prod. and yeah, radar is the net behind the gate, not the gate itself. do you put the captcha on every checkout session or only after a failed attempt from the same ip?
Only after a failed attempt from the same IP. Card testing fails fast: the declines cluster on the first handful of attempts, so the first failure from an IP is your cheapest tripwire, and putting the captcha on every session taxes every real customer to stop a threat that hasn't shown itself yet. Two honest caveats: serious testers rotate IPs, so IP-scoped captcha alone buys you an hour at best, and the thing that actually holds the line is the velocity gate keying on harder-to-fake signals (card count, BIN spread, email domain) with the captcha as the visible hand-off once those fire. Practically: no captcha, first decline from that IP trips a soft challenge, second one blocks the session and you never see the rest of the run.
first decline from an ip trips a soft challenge, second one kills the session. that's a lot nicer than putting a captcha in front of every real customer. agree the velocity gate on card count is what actually holds, the ip part just buys time.
escalate-on-second-failure is the right shape since one legit decline from a shared VPN ip now costs a real customer an extra click instead of the session. the thing that makes the velocity gate durable is checking distinct bins per ip and distinct ips per bin, because the cheapest bots rotate both and a single-axis counter misses them. that cross-check plus a network token refresh after a failed retry keeps the whole flow reversible for the customer.
distinct bins per ip and distinct ips per bin is a really good way to put it, a single counter only catches the lazy ones. how do you pick the window for that check, an hour or something shorter like 10 minutes?