The first thing every team reaches for when verification abuse shows up is a per-account cap: no more than N codes per user. It feels obviously correct. It is also, on its own, almost useless against a determined attacker.
The account-rotation bypass
Here is what real abuse looks like, taken from the production incident this product was built against:
- Per-account attempts topped out at 6–16 — well under any sane cap.
- But single addresses carried 4+ accounts and 28+ attempts.
- Sequential user IDs from one address, minutes apart, gave the automation away.
A user-keyed counter essentially never fired. The attacker simply registered another account whenever one approached the limit. The cap was real; it just measured the wrong thing.
Why no single key is enough
The deeper problem is that any single-key counter can be defeated by rotating that key. Accounts, phone numbers, dial codes, and even IP addresses are all cheap to change. One actor in the source data worked three addresses in 25 minutes — five sends, then two, then three. No address counter reached five. The account counter saw all ten. Neither key alone was sufficient.
What actually holds: several keys at once
The answer is not a lower cap — a genuinely heavy legitimate user already lives near the ceiling. The answer is to count on several keys simultaneously, in one atomic evaluation: per user, per number, per address, and per network. An attacker who rotates one key still trips another.
And crucially, the network key (a /24 or /48) is the one velocity provably cannot replace. A distributed actor spreads thinly across addresses precisely to stay under every per-address cap — and only an aggregate sees them.
That is the layer that needs traffic from everybody else to work, which is why it is a service and not a snippet.