Velocity detection alone is a trap
Rate limits feel like the answer to card testing. But every single-dimension defense names the exact thing the attacker should change next. The trap isn't that velocity is wrong — it's that it's alone.
Most teams meet card testing the same way. The declines spike, someone opens the gateway dashboard, and they add a rule: cap attempts per IP, cap declines per card prefix, throttle anything that moves too fast. It feels like the answer because it stops this burst. The graph flattens. The ticket closes.
The problem is that a velocity rule announces, in plain terms, the one dimension you’re counting. And the dimension you count is the dimension the attacker varies. You haven’t closed the door — you’ve labeled it.
A single signal tells the attacker exactly what to change.
Every block names its own bypass
Watch what happens at each rung. The defense is reasonable. The counter is one variable change, and it’s cheap.
- Block by IP — and the attacker rotates IPs. Residential proxy pools and commodity botnets make source addresses disposable; an attacker buys thousands for the price of a coffee. The counter you keyed on is the counter they discard first.
- Limit by card prefix — and the attacker spreads across prefixes and slow-drips under the window. Your rule trips at N per minute, so they send N minus one and add more sources. Nothing about the rule is hidden from them; the threshold is observable by feel.
- Throttle by session or cookie — and the attacker throws the session away. A script has no reason to keep state. Every attempt starts fresh, so a per-session counter never accumulates.
- Filter by user-agent or headers — and the attacker forges them. Those fields are attacker-controlled strings. Filtering on them filters on what the attacker chose to send you.
Each line is the same move. You pick a dimension; they vary that dimension. Defense-by-one-signal is a roadmap with the destination circled.
A single signal doesn’t stop an attacker. It tells them which knob to turn.
So what’s actually expensive to evade?
Notice what isn’t on that list: varying all of those dimensions at once, coherently, while still looking like a real person buying something.
Rotating IPs is cheap. Distributing across prefixes is cheap. Forging headers is cheap. Doing all of it simultaneously — and pacing slowly enough to stay under every counter — is still cheap, but it produces a tell. The moment a session slows down, rotates, and distributes specifically to dodge thresholds, its behavior stops resembling a human and starts resembling a machine evading thresholds.
That’s the signal that doesn’t have a one-move counter. Not how fast the requests come, not where they come from, but how the session behaves against a baseline of real traffic: the rhythm of interaction, the shape of the flow, the things a person does on the way to a purchase that a card-testing script has no reason to do. You can rotate every identifier and still fail to act like a customer.
Velocity tells you how fast. It never tells you who.
This is why no single layer is the system. Velocity is real and it stays in the stack — but as one input, not the verdict. Edge integrity catches forged and spoofed clients. Step-up raises the cost of automated attempts. Behavioral analysis scores whether the session looks like a buyer at all. Defeating any one layer doesn’t defeat the others, and the only way to evade all of them at once converges on a single requirement: behave like a legitimate user. That is the one thing a card-testing operation can’t do cheaply, because it’s the one thing the operation isn’t trying to do.
Layered signals don’t add. They multiply the attacker’s cost, because each layer they beat leaves the next one untouched, and the bypasses don’t compose.
Stop counting attempts. Start scoring sessions.
Stop asking “how many attempts came from this IP this minute?” and start asking “does this session behave like someone who intends to buy?” The first question has a cheap answer the attacker controls. The second one doesn’t, because it isn’t about any single identifier the attacker can rotate — it’s about coherent behavior the attacker would have to manufacture from nothing.
Cardvera is built as composition for exactly this reason. Behavioral analytics anchors the stack, with velocity sitting inside it as one layer among several rather than standing in front as the whole defense. Velocity alone is a trap because it’s alone. The fix isn’t a better counter. It’s not counting.