The clearest card testing attack example I know is not a case study. It is a comment a founder left on Hacker News in April 2020, reporting 75,000 stolen cards validated through their dormant startup's checkout in 28 days, against zero completed transactions.
Nothing was stolen from them. There was no revenue to take and no product to ship. The checkout was the instrument and the cards were the inventory.
What follows is that incident reconstructed. I am explicit about which parts the poster stated and which parts come from Stripe's documentation, because the difference matters when you go looking for the same shape in your own account.
What the thread says
philshem, on Hacker News: "When I checked the dashboard logs, I found that there had been 75k tokens created in the last 28 days (100% card testing). That's 75k stolen credit cards that my website (and Stripe) have helped to validate."
The rest of the comment is the part worth reading twice. The startup was dormant, so checking the dashboard was not part of a daily or even a monthly routine. The attack was not small purchases but started and cancelled transactions. Nothing alerted.
The poster found out when someone whose card had been stolen emailed them, having somehow seen the company name on their statement. The email looked enough like phishing that contacting support was the cautious move rather than the panicked one. Support read the logs and helped roll a new public key. Asked why 75,000 token creations had triggered nothing, the answer was that it was not a feature.
That is the entire stated record: 75,000 tokens, 28 days, zero completed transactions, one public key, no alert.
How the checkout became a card validator
The mechanics are documented by Stripe, and they explain why this shape produces nothing to notice.
The endpoint was public by design. A publishable key is safe to expose; that is its job. Stripe's card testing documentation states the consequence plainly: "Card testers can use your publishable key and use it to retry a large number of payments on your website." Nothing was breached. The attacker used the checkout the way it was built to be used.
Card setup beats payment. Stripe explains the preference in the same page: card setup is "a method preferred by fraudulent actors, because card validation and authorizations during card setup don't typically show up on cardholder statements." That is why this ran for four weeks. Tokens and abandoned transactions produce issuer responses without producing statements, so the cardholders had nothing to report.
The issuer's answer is the product. Each attempt is a query, and a live card is worth more than the dollar it was never charged. The resale mechanics are in our piece on card testing on Stripe.
A dormant account is the ideal host. Low traffic means no baseline to disturb, and nobody logs in to see the graph move.
Every stage had a signal
A conversion ratio that inverted. 75,000 token creations against zero completed transactions in the same period. The poster asked for exactly this alert and did not have it: "I'd be happy just to get an alert that 75k tokens were created in four weeks, while exactly 0 (zero) completed transactions in the same period." That is one query, run hourly.
Distinct cards per device. One legitimate person uses one or two cards. A tester uses thousands. The amount is a decoy; the card diversity is the tell, and it is countable per device long before it shows up in a revenue graph.
The reason to key that counter on the device rather than the IP is that the device survives what the attacker can change cheaply. Rotating egress is a proxy subscription. Rotating a browser fingerprint takes real effort, and a script that runs no JavaScript at all produces no device token, which is its own signal.
Nonsense contact data. Not in this report, but the usual companion. Stripe's symptom list names "a spike in suspicious payments with low transaction amounts, often with nonsensical customer names and emails." Addresses at example.com are the common case, and that domain is reserved for documentation, so no customer types it on purpose. This is a string match, not a probability.
Nobody was running any of those queries, because Stripe's default surface reports revenue, and this attack produced no revenue to interrupt.
startages, on Hacker News in May 2026, on defending client stores: "There are a lot of card testing attacks that Stripe doesn't care to handle as well as a lot of other fraud techniques, but there is always a pattern, especially automated ones. There is country, IP range, certain behavior (eg; no js, or direct api calls..etc)."
Where the Radar bill comes from
This is where a quiet attack turns into an invoice, and it is documented rather than inferred. Radar "screens all payment attempt types (for example, successful, declined, blocked, and flagged for review)," and the Standard, Plus and Pro plans "charge a fee for each screened transaction". Stripe's pricing page lists Radar starting at $0.05 per screened transaction.
Blocked attempts are screened attempts. You are billed for the fraud Radar catches on your behalf, at the same rate as the payments you keep.
What that costs is arithmetic, not a claim about this account. The poster never says which plan they were on, and Radar Lite carries no per-transaction fee. Run 75,000 screened attempts through a paid plan at five cents, though, and you have $3,750 for a month in which nothing sold. The attacker sets the volume, and the engine's success at blocking does not reduce it.
A Checkout Session is a container, and a script can retry several cards inside one. Your session count is not your attempt count, and your attempt count is what prices the invoice.
What would have stopped it, and when
Ordered by where the cut happens, earliest first.
| Stage | Signal available | Control | Where it lives |
|---|---|---|---|
| Attempt 10 | Same device, second distinct card | Per-device card counter | Your server, pre-Stripe |
| Attempt 50 | One IP, no JS, nonsense email | Velocity and email quality screen | Your server, pre-Stripe |
| Hour 1 | Attempts climbing, completed orders flat | Ratio alert | Your monitoring |
| Day 1 | A secret key used from a network you don't own | Access policy on the key | Stripe dashboard |
| Day 28 | Blocked payment spike | Radar | Stripe, billed per attempt |
One of those is free and takes about ten minutes. If the key that leaks is a secret or restricted key, you can attach an access policy to it: specific IPv4 addresses or CIDR ranges, or under the Advanced type a set of allowed ASNs and countries, with anonymous VPNs, public proxies, residential proxies and Tor exit nodes blocked as sources. "If someone attempts a request using a key they can't access, Stripe blocks the request and notifies you." Stripe recommends configuring policies on all live mode keys. Almost nobody does.
None of that helps in this teardown. A publishable key is meant to be public, and the attempts arrived through the checkout exactly as designed.
Stripe's first answer for that shape is its own front end. If you use a current Payment Element or Checkout integration, you get rate limiters, risk models and CAPTCHA triggers that Stripe tunes during an active attack, and the same documentation is blunt about the tradeoff: how well those controls work "depends on your integration and what risk factors you send to us." A direct integration that posts a card and an amount gives the models almost nothing to work with. Send the IP, the email and the billing address before you go looking for anything more sophisticated.
Past that, the controls that bite live in your own request path, before you create the Stripe object, and they are counters rather than rules about amounts. Distinct card fingerprints per device in the last hour. Attempts per IP measured against your own baseline rather than a number you guessed. Email domains no human uses. The thresholds, and why per-IP counting alone is untrustworthy, are in our post on velocity checks.
This is the tool I built for that gap. Portreeve is an abuse firewall: one API call at checkout_attempt before you create the Stripe object, returning allow, review or block with reason codes in under 100 ms, with velocity counters per IP, per device and per card, plus card probe detection for small-amount and $0 authorization ladders. Two of the block-strength card rules read a device-keyed counter, so the browser fingerprint snippet is required for card testing to be blockable at ordinary amounts. A review verdict never blocks the user: the flow proceeds, the event lands in a queue, and a later deny reaches your server as a signed webhook so you can revoke, which is what lets the engine flag aggressively without costing you real signups. Details on handling verdicts in the docs.
Two smaller things, both cheap. Return a generic decline on every rejection and never the issuer's reason, because the reason is the data the tester came for. And alert on ratios rather than totals: attempts per completed order, hourly, at a threshold you would notice at dinner. A dormant account with no sales needs that alarm more than a busy one, not less, because there is no revenue graph to look wrong.
Cut before the meter starts
Every control Stripe gives you fires after the attempt exists, which is after the meter has run. Radar blocking an attempt is Radar charging you for that attempt. The screening that changes your invoice is the screening that happens before the request reaches Stripe at all.
If you want a verdict on checkout attempts before they get there, Portreeve is in open beta with a free tier of 1,000 screened events a month and no card required: start here.