A rate limit on one key is defeated by rotating that key. If you throttle per account, the attacker uses more accounts. Per number, they use more numbers. Per IP, they rotate IPs. Any single-dimension limit teaches your adversary exactly which dimension to spread across — and pumping traffic is built to spread.
Every single key fails alone
Per-account limits assume one account per attacker. Farms sign up thousands. The account you're throttling sends three requests and is never seen again.
Per-number limits assume a target number gets hammered. In an AIT attack each premium number receives a handful of sends across the whole campaign. Nothing individually looks abusive.
Per-IP limits assume a source. Attackers have proxy pools and carrier NAT on their side — one IP, many users, or one user, many IPs. Either way the counter lies.
Each limit is correct about a real threat and blind to the others. Run them separately and the attacker simply moves to whichever key you're not watching.
Check every key at once
The fix is to stop treating them as separate limits. A single send is evaluated against account, phone, network, and IP counters simultaneously, and it passes only if every window has room.
Now rotation doesn't help. Spread across accounts and the network counter catches it. Spread across networks and the account counter catches it. To beat all four dimensions at once, the attacker has to look like genuinely distributed organic traffic — at which point they've lost the volume that made the attack pay.
Atomicity is not optional
Checking four counters is only correct if the check and the record happen as one indivisible step. Read-then-write in application code opens a race: two concurrent requests both read "under the limit," both proceed, and the cap leaks under exactly the concurrent load an attack produces.
Verifence evaluates all four windows in a single Redis Lua script. The counters are read, judged, and incremented atomically — no gap for a concurrent request to slip through.
Record all-or-nothing
The script writes the send only if every counter passed. A rejected attempt increments nothing, anywhere.
That matters more than it looks. It means a blocked request never consumes quota, so an attacker can't burn down a legitimate user's velocity budget by firing denials, and your counters only ever reflect sends that actually happened. The limit measures reality, not attempts against it.
One atomic check across four keys, recorded all-or-nothing. That's a velocity limit rotation can't walk around.