Stop Card Testing Bots, Auth Floods & Chargeback Fallout
Bots use checkout and donation forms to validate batches of stolen cards, and you can pay twice: authorization fees now, disputes weeks later. Maskbreak checks each checkout for the automation and proxies behind the run in one server-side call, before the payment intent exists. A blocked attempt never reaches your processor.
What you're up against
Small-amount authorization floods
Bots cycle stolen card batches through your checkout with tiny authorizations to find which cards are live. On many processor plans every authorization attempt carries a fee, approved or declined, and a wall of declines drags down the approval rate your acquirer sees.
Donation forms as free card validators
Any form that accepts an arbitrary amount is a perfect card tester: no inventory, no shipping address, instant confirmation that a stolen card works. Nonprofits and tipping flows get hit hardest precisely because they keep friction low.
Chargebacks land weeks after the test
Cards validated on your site get drained elsewhere, and the test charges that went through come back as disputes. Enough of them push your chargeback ratio toward the card networks' monitoring programs, which carry fines and can end with the processor closing the account.
Velocity rules that proxies walk around
Per-IP rate limits and per-card counters assume the attacker keeps one address. Testing runs rotate residential proxies so every attempt arrives from a fresh "clean" IP, and your velocity rules never fire.
What Maskbreak does for you
- Check every checkout before the payment intent is created, so once you enforce, a blocked attempt never reaches your processor
- Flag scripted and headless browsers (
automation_detected) and fake browsers (antidetect_browser) on payment pages; both block by default - Block proxies and Tor exits by default, residential proxies included; a VPN on its own is flagged for review, and a cloud-server address alone stays allow with
datacenter_asnlisted - Return
device.visitor_idwhenever the browser check completes, so you can count card attempts per browser instead of per IP address - No CAPTCHA or challenge from Maskbreak: the answer is an API response your server acts on
running hundreds of small authorisations is the tell. A worked model of a store's checkout under a card-testing run, and where the check goes. Illustrative scenario, not a customer result.
Read the PlaybookWhere the check goes
One call site covers this vector: the server route that creates the payment intent — your checkout submit and any donation or tipping endpoint that charges a card. Run the check after the form posts but before you talk to your processor; once you enforce, a block means the stolen card is never authorized, so there's no auth fee and nothing to charge back. The client snippet rides along in your existing payment page and adds the two tokens your server sends to Maskbreak. The example starts in watch mode: it logs each verdict, lets every request through and fails open if Maskbreak is unreachable. After a few days, compare the log with the Events in your dashboard, then uncomment the enforce line; the integration guide has the full enforce checks.
// Score the checkout before creating a payment intent // Watch mode first: log the verdict, always continue, fail open const body = req.body || {}; // sentinel.js adds monocle + sentinel_fp to your form let risk = null; try { const verdictRes = await fetch('https://maskbreak.com/v1/evaluate', { method: 'POST', signal: AbortSignal.timeout(5000), headers: { 'Authorization': 'Bearer ' + process.env.MASKBREAK_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ token: body.token || body.monocle, // network evidence fingerprintEventId: body.fingerprintEventId || body.sentinel_fp, // device evidence tz: body.tz || body.sentinel_tz }) }); if (verdictRes.ok) risk = await verdictRes.json(); } catch {} // unreachable or timed out: your handler still runs console.log('[maskbreak]', JSON.stringify({ decision: risk?.decision ?? null, reasons: risk?.reasons ?? null, risk_score: risk?.risk_score ?? null, degraded: risk ? !!risk.degraded : null })); // Enforce later: if (risk?.decision === 'block') return res.status(403).json({ error: 'Request rejected' });
What a card-testing script behind a residential proxy gets back. Example response, selected fields:
{
"decision": "block",
"risk_score": 90,
"reasons": ["proxy_detected", "automation_detected"],
"network": { "vpn": false, "proxy": true, "datacenter": false,
"tor": false, "residential": false,
"service": "<proxy network, when known>" },
"device": { "automation": true, "antidetect": false,
"visitor_id": "<opaque visitor id>", "tampering_score": 0 }
}Proxy and automation are both hard signals, so the default is block. network.residential is false because the address is a proxy, even when it sits on a home connection. Route on decision, not on a risk_score cut-off of your own (why). Every response also reports its own processing time in evaluated_in_ms; /status publishes measured figures.
The signals that matter here
Automation, before the first authorization. Nobody types stolen card numbers by hand — card testing is scripted end to end, usually headless Chrome with stealth patches that pass simple bot checks. Maskbreak's device check looks for the traces an automated or tampered browser leaves. When it finds them, device.automation comes back true with automation_detected in the reasons; a fake-browser profile shows as device.antidetect and antidetect_browser. Either one is a block by default, before the first authorization is attempted.
One browser, many cards. A testing run rotates card numbers, emails and IP addresses, but often keeps the browser. Count attempts per device.visitor_id in your checkout handler (say, more than a few different cards from one visitor in an hour) instead of per IP. Do not use device.times_seen as that counter: it is how often Maskbreak has seen the device across every site that uses it, not on your checkout. high_activity_device is listed when a device is unusually busy; it is a soft signal that adds a little to risk_score and never changes the decision on its own.
Network context on the payment path. Most real buyers check out from home or mobile networks. Card-testing traffic often runs through proxies, including residential ones: network.proxy is true, proxy_detected is in the reasons and the default is block, as it is for a Tor exit. A VPN on its own can be privacy hygiene, so it is flagged for review, not blocked, and the operator is named in network.service when known. A cloud-server address alone stays allow, with datacenter_asn listed for your own policy.
A blocked attempt can't become a chargeback. An authorization that reaches your processor can cost a fee, approved or declined, and an approval on a live stolen card can come back as a dispute weeks later. Checking before the payment intent exists means a blocked attempt ends in your own backend: no authorization, nothing to dispute, and no new friction for real buyers, because the answer is an API response rather than a challenge. Allowed attempts still go through your processor's own checks.
Common questions
Start protecting your checkout
Free plan: 10,000 visitor checks a month, every signal included, no card. A testing run costs one check per attempt: Free is capped at 1,000 checks an hour, and paid plans from €29 a month raise the cap (Starter 5,000, Growth 20,000, Scale 50,000 an hour). Start in watch mode and enforce when the log looks right.