One-time passcodes are still the most widely deployed second factor on the internet, mostly because they work on any phone, in any country, with no app install. But the same simplicity that makes SMS OTP easy to adopt also makes it easy to abuse. Most incidents are not the result of broken cryptography — they come from loose rules around how codes are generated, how long they stay valid, how many times they can be requested, and what happens when a phone number quietly changes hands.
This guide walks through the controls that matter most, in roughly the order an engineering team should implement them.
Know the three attack shapes
Almost every OTP abuse case falls into one of three categories:
- Code guessing. The attacker has the victim's phone number and username, triggers an OTP, then brute-forces the code. A 4-digit code with unlimited attempts is trivially guessable.
- Code interception or social engineering. The attacker convinces the user to read the code aloud, or phishes it through a fake login page that relays it in real time.
- Number takeover. SIM swap, port-out fraud, or recycled numbers. The code is delivered correctly — to the wrong person.
There is also a fourth category that costs money rather than security: OTP pumping (sometimes called SMS traffic pumping or artificially inflated traffic), where bots hammer your signup endpoint to generate messages to numbers on routes that pay out revenue to someone else. Rate limiting is the main defence here too.
Rate limiting: four dimensions, not one
A single global limit is almost useless. Effective rate limiting works on several axes simultaneously:
- Per destination number. Cap how many codes a single phone number can receive in a given window — for example, a small number per few minutes and a slightly larger daily cap. Legitimate users rarely need more than two or three.
- Per account or session. Independently cap requests coming from a single user record, device fingerprint or session token, so an attacker cannot rotate numbers from one session.
- Per IP and per subnet. Useful against scripted abuse, though easily defeated by residential proxies, so never rely on it alone.
- Per country or prefix. If your product has no users in a given country, do not send OTPs there. Explicit allow-lists are one of the cheapest and most effective anti-pumping controls available.
Pair these with exponential backoff: the second resend waits longer than the first, the third longer still. Add a verification attempt limit as well — typically a handful of wrong guesses before the code is invalidated and a new one must be requested. Limiting requests without limiting attempts leaves the brute-force door open.
Expiry windows and code design
A code should live just long enough for a user to switch apps and type it. Practical ranges sit somewhere between two and ten minutes; longer windows mainly help attackers. A few rules that go with it:
- Single use. Invalidate the code the moment it is verified, successfully or not, after the attempt cap.
- Invalidate on reissue. When a user requests a new code, the previous one should die immediately. Keeping several valid codes alive multiplies the guessing surface.
- Six digits minimum. Four-digit codes are still common but offer far less margin, especially if your attempt limits are generous.
- Cryptographically secure randomness. Never seed codes from timestamps or sequential counters.
- Constant-time comparison and server-side storage of a hash, not the plaintext code.
Message wording matters more than teams expect. A good OTP message names the brand, states the action being authorised ("login", "payment confirmation"), and includes a short warning not to share the code. Users who know what the code is for are much harder to socially engineer. Template-based sending — where the body is fixed and only the code is variable — also keeps content compliant with operator rules in markets that require pre-registered sender IDs and templates.
SIM-swap awareness
SIM swap is the hardest one, because delivery looks perfectly normal from your side. You cannot prevent it; you can reduce the blast radius:
- Add friction after a number change. When a user updates their phone number, impose a cooling-off period before high-risk actions such as withdrawals, password resets or adding payout accounts.
- Watch for behavioural signals. A login from a brand-new device, in a new country, immediately followed by a sensitive action, deserves step-up verification beyond SMS.
- Do not use SMS as the sole recovery path. If a phone number can reset a password that in turn unlocks everything, the OTP is not a second factor — it is the only factor.
- Offer stronger options where you can. Authenticator apps and passkeys are not realistic for every audience, but they should be available to users who want them, particularly on accounts with financial exposure.
- Use carrier-side signals where available. Some markets expose SIM-change or number-porting lookups through operators or aggregators. Availability and pricing vary by country, so treat it as a regional enhancement rather than a universal control.
Monitoring: the control that catches what rules miss
Static rules age badly. What keeps them honest is measurement. Track, per country and per route: OTP request volume, delivery rate, verification conversion rate (codes verified divided by codes sent), and average time-to-verify. A sudden volume spike paired with a collapsing conversion rate is the classic signature of pumping or scripted abuse — real users verify the codes they ask for.
This is where delivery telemetry stops being a reporting nicety and becomes a security input. Real-time delivery reports and webhook callbacks let you feed message-level status straight into your fraud rules instead of discovering a problem at the end of the billing cycle. On UIPAPP, OTP traffic runs over priority routes with template support, and per-message status is available through the API and webhooks, so the same data that proves deliverability can also drive your anomaly alerts.
A short implementation checklist
- Six-digit, cryptographically random, single-use codes
- Expiry under ten minutes, invalidated on reissue
- Attempt limit per code, request limits per number, session, IP and country
- Exponential backoff on resends, with a clear user-facing message
- Branded message copy that states the action and warns against sharing
- Cooling-off periods after phone number changes
- Alternative second factors for high-value accounts
- Dashboards on send volume, delivery rate and verification conversion by country
None of these controls is exotic, and none of them requires replacing your authentication stack. Implemented together, they close most of the gaps that OTP fraud relies on — and they make the rest a great deal easier to spot early.