A well-built OTP flow uses a random 6-digit code that expires within a few minutes, allows only a handful of attempts, enforces a cooldown before each resend and sends a short message with your brand name, the code and a “do not share” warning. Add a fallback channel for users the first channel cannot reach, and measure how many codes are actually verified; an SMS verification API lets you set these rules once and adjust them per request.
Below are the defaults we recommend, the reasoning behind each one, and the exact formats Android and iOS need to autofill codes.
What are good default OTP settings?
Start with these values and tune them using your own conversion data:
| Setting | Recommended default | Why |
|---|---|---|
| Code length | 6 numeric digits | NIST’s minimum for out-of-band secrets; easy to type and autofill |
| Code lifetime | 3 to 5 minutes for logins; shorter for risky actions | Long enough for slow routes, well inside NIST’s 10-minute maximum |
| Attempts per code | 3 to 5, then invalidate | Keeps guessing odds tiny |
| Resend cooldown | 30 to 60 seconds, growing after each resend | Stops impatient double-sends and scripted abuse |
| Resends per number | A small daily cap | Limits cost and SMS pumping exposure |
| Fallback | App channel first where it is common, SMS as the safety net | Reaches users who lack the first app |
How long should an OTP code be?
Six digits is the common choice for good reason. NIST SP 800-63B-4, finalised on 31 July 2025, says verifiers “SHALL generate random authentication secrets that are at least six decimal digits (or equivalent)”.
A 6-digit code has one million possible values. If you allow three attempts, an attacker who guesses at random has a 3 in 1,000,000 chance per code. A 4-digit code with the same limit is a hundred times weaker. Longer codes add little security for a one-time 2FA step and make typing harder.
Generate codes with a cryptographically secure random generator, never from timestamps or counters, and store them hashed on your side if you generate them yourself. Keep codes numeric unless you have a specific reason not to: digits are faster to type on a phone keypad and work with every autofill method below.
How long should an OTP stay valid?
NIST requires out-of-band authentication to be considered invalid unless completed within 10 minutes. That is a ceiling, not a target. For most login and sign-up flows, 3 to 5 minutes covers slow international routes and users switching between apps.
Use shorter lifetimes for high-risk actions such as password resets, payout detail changes or large transfers. If you use fallback, make the per-channel timeout much shorter than the code lifetime, so the second channel still has time to deliver before the code expires. Always invalidate the code after one successful use.
How many attempts and resends should you allow?
Treat attempts and resends as two separate limits:
- Attempts per code: 3 to 5 wrong entries, then invalidate the code and ask the user to request a new one.
- Resend cooldown: disable the resend button for 30 to 60 seconds, and lengthen the wait after each resend.
- Resends per number and per session: cap them per hour and per day.
- Per-account lockout: NIST also requires verifiers to limit consecutive failed attempts on an account, with a maximum of 100. Most consumer apps set a far lower threshold.
Apply rate limits by IP address and device as well as by phone number. Attackers who cannot guess codes will try to trigger as many sends as possible instead; our guide to SMS pumping fraud covers those controls in detail.
What should the OTP message say?
A good OTP message is short, branded and hard to misuse:
- Brand name first so users know who sent it, even in the lock-screen preview.
- Code early in the message, not buried after a paragraph.
- “Do not share this code”, which helps against social engineering calls.
- Expiry time, so users know whether to request a new code.
- No links in the message body. Links train users to tap links in OTP messages, which phishers exploit.
- The user’s language. Localised templates convert better.
Example: ExampleApp: your code is 482913. It expires in 5 minutes. Do not share it with anyone.
Keep templates in the GSM-7 character set where you can. Accented characters or emoji can switch the message to Unicode encoding, which lowers the character limit per SMS part. Our Unicode SMS checker shows the encoding and part count before you send.
How do you make OTP codes autofill on Android and iOS?
Autofill removes the typing step, which is where many users drop off. Each platform has its own rules.
Android: SMS Retriever API
Google’s SMS Retriever documentation says the message must be no longer than 140 bytes, contain the one-time code, and include an 11-character hash string that identifies your app. Google’s example places the hash on its own line at the end:
Your ExampleApp code is: 123ABC78
FA+9qCX9VSu
The hash is derived from your app’s package name and signing certificate, so debug and release builds have different hashes. If you cannot control the message format, Google’s SMS User Consent API is the alternative: it asks the user for permission to read one message.
iOS and Safari: one-time-code and domain-bound codes
On iOS, mark the input field as a one-time code (textContentType set to oneTimeCode in UIKit, or autocomplete=”one-time-code” on the web) and the keyboard suggests codes from incoming messages. For stronger phishing protection, Apple’s domain-bound code format requires the last line to contain your domain prefixed with @ and the code prefixed with #, separated by a space:
Your ExampleApp code is 123456.
@example.com #123456
AutoFill then suggests the code only on your website or in an app associated with that domain. The same last-line format is used by the WebOTP API in Chromium browsers. Nothing may appear before the @domain on that last line.
If you need both the Android hash and the domain-bound line, keep the domain-bound line last and test the combined message on real devices before launch.
Which fallback order should you use?
Send on one channel at a time and move to the next only when the first has not delivered within your timeout. A common pattern is WhatsApp or Viber first in markets where those apps are widely installed, with SMS as the universal safety net, since SMS reaches any mobile number without an app or data connection. Where app usage is fragmented, start with SMS.
Keep the same code across channels within one verification, so a late SMS does not invalidate a WhatsApp code the user has already opened. Our guide to building an OTP flow with smart fallback covers channel design in more depth.
Is SMS OTP still acceptable under NIST guidance?
Yes, with conditions. NIST SP 800-63B-4 states that “use of the PSTN for out-of-band verification is restricted”. A restricted authenticator can still be used, but verifiers must offer alternative authenticator types to all subscribers, should warn users about the limitations before binding, and must address the extra risk in their risk assessment. NIST also says email “SHALL NOT be used for out-of-band authentication”.
In practice: keep SMS as a widely supported factor for sign-up and login, offer an authenticator app or passkey for users who want stronger protection, and use additional checks for high-value actions.
What should you monitor after launch?
- Verification conversion: codes verified divided by codes sent, by country, channel and network.
- Time to verify: the gap between the send and a correct entry.
- Fallback rate: how often the first channel times out.
- Cost per successful verification, not cost per message.
- Delivery statuses: DLR errors such as absent subscriber or invalid number, explained in our SMS error codes guide.
A sudden fall in conversion in one country usually means a routing issue or an attack. Alert on it.
How do you send and check an OTP with the SMS.to Verify API?
Create a verification with your Verify App’s guid, the recipient in international format and the channel order. Each channel has a priority and an expiry_time, the window before Verify moves on to the next channel:
curl -X POST https://verifyapi.sms.to/api/v1/verifications/create \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"guid": "3-1748345-bcef-0342-6bef5c26-04f64e7",
"recipient": "+35799999999",
"reference": "signup-000123",
"callback_url": "https://example.com/verify/callback",
"channels": [
{ "channel": "whatsapp", "priority": 1, "expiry_time": 30 },
{ "channel": "sms", "priority": 2, "expiry_time": 30 }
]
}'
When the user enters the code, confirm it with the verification ID returned by the create call:
curl -X POST https://verifyapi.sms.to/api/v1/verifications/confirm \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "3-1749636-f945-113c-a0499efc-4f9372d",
"password": "482913"
}'
The response returns success true for a valid, unexpired code and success false if the code is wrong, expired or already used. Only grant access on success true.
How SMS.to Verify applies these best practices
With the SMS.to Verify API you create one Verify App and set your defaults there: channel order across SMS, WhatsApp, Viber and Telegram, code expiry, code length and format, message templates and language. The application settings also cover the maximum number of tries. You can override channel order, expiry, code length or format, template and locale on any single request, for example to A/B test a new region or shorten expiry for a risky action.
If you prefer to generate codes yourself, the client-generated OTP option lets you pass your own code while Verify handles delivery and checking. Delivery and verification webhooks, searchable logs and charts show completion and drop-off, and each result records which channel delivered. On billing, the Verify fee applies only when the user verifies; app channels are charged only if they deliver, while SMS is charged when sent.
FAQs
Is a 4-digit OTP secure enough?
It is weaker than it looks. NIST requires at least six decimal digits for out-of-band secrets, and a 4-digit code gives an attacker a hundred times better odds per guess.
How long should an OTP be valid?
Three to five minutes suits most logins and sign-ups. NIST sets a 10-minute maximum, and risky actions deserve a shorter window.
Why is my OTP not autofilling on Android?
The SMS Retriever API needs the 11-character hash for the exact build you are testing, and the message must stay within 140 bytes. Debug and release builds have different hashes.
Should I send the OTP on several channels at once?
No. Send on one channel, wait for your timeout and then fall back. Parallel sends cost more and confuse users with duplicate messages.
Ready to build your flow? Create a free account, no credit card needed, and send your first verification in minutes.