---
title: "Stop card testing on your checkout · Agent Wranglers"
url: https://agentwranglers.se/playbook/payments/refunds-disputes-and-fraud/card-testing
updated: 2026-10-20
licence: "All rights reserved"
---

# Stop card testing on your checkout

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Refunds, disputes and fraud](https://agentwranglers.se/playbook/payments/refunds-disputes-and-fraud)
- Rule: Limit payment attempts per account, IP address and card, and ask for a challenge after repeated declines. Then attackers cannot test stolen cards on your checkout.
- How: Card testers send thousands of small payments to find cards that work. Each decline costs a fee and hurts your standing with the networks. Allow a few attempts, such as 5 an hour per account, 20 per IP address and 3 per card fingerprint. After 3 declines in a session, require a CAPTCHA or sign-in. Turn on the provider's card testing protection. The general method is in [limit requests](https://agentwranglers.se/playbook/adversarial-testing/abuse-and-limits/limit-requests) and [free tier abuse](https://agentwranglers.se/playbook/adversarial-testing/abuse-and-limits/free-tier-abuse).
- Check: A script sends 30 payments with a declining test card from one IP address within a minute. After the limit, your server answers 429, and the provider receives no further attempts.

Example:

```js
const LIMITS = [
  { key: req => `pay:acct:${req.accountId}`, max: 5, windowSeconds: 3600 },
  { key: req => `pay:ip:${req.ip}`, max: 20, windowSeconds: 3600 },
  { key: req => `pay:card:${req.cardFingerprint}`, max: 3, windowSeconds: 3600 },
]

async function allowPaymentAttempt(req) {
  for (const limit of LIMITS) {
    const count = await redis.incr(limit.key(req))
    if (count === 1) await redis.expire(limit.key(req), limit.windowSeconds)
    if (count > limit.max) return false
  }
  return true
}
```

```python
LIMITS = [
    (lambda req: f"pay:acct:{req.account_id}", 5, 3600),
    (lambda req: f"pay:ip:{req.ip}", 20, 3600),
    (lambda req: f"pay:card:{req.card_fingerprint}", 3, 3600),
]

def allow_payment_attempt(req) -> bool:
    for key_of, max_attempts, window_seconds in LIMITS:
        key = key_of(req)
        count = redis.incr(key)
        if count == 1:
            redis.expire(key, window_seconds)
        if count > max_attempts:
            return False
    return True
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers payments playbook: https://agentwranglers.se/playbook/payments/refunds-disputes-and-fraud/card-testing`.

- Previous: [Keep your dispute ratio well below the network limits](https://agentwranglers.se/playbook/payments/refunds-disputes-and-fraud/dispute-ratio)
- Next: [Screen risky payments before fulfilment](https://agentwranglers.se/playbook/payments/refunds-disputes-and-fraud/fraud-screening)
- [Refunds, disputes and fraud](https://agentwranglers.se/playbook/payments/refunds-disputes-and-fraud)
