OTP flows that don’t lock people out
A user is trying to verify their identity to receive a payout. The OTP arrives ninety seconds late because the carrier is throttled in Lagos. The resend timer ran out thirty seconds ago. They tap resend.
Two valid codes are now in transit. They enter the first one. It fails. The second code invalidated the first. They’re locked out for an hour.
That was a real ticket on a KYC product I worked on. We saw it most days, in different shapes.
- 0.0sCode sent · SMS only
- 0.0sCode sent · SMS, voice fallback ready
The OTP screen has no second chance
Most of the product can tolerate a bad moment. A slow page can be excused. A failed search can be retried. The user is in the flow, they want the thing on the other side, they’ll forgive a bit.
OTP doesn’t get that grace. The user has already filled out a form, taken a photo of their ID, agreed to terms. They’re at the last step, the one between them and the payoff. If this step fails, every step before it failed.
The design rule that fell out of this: nothing on this screen is allowed to be silent.
Five small decisions
Each of these is one or two lines of code. None of them needs a redesign. All of them saved users we used to lose.
- Don’t invalidate the old code on resend. When the user taps resend, the previous code stays valid until its window expires. Accept either. The race where the late code arrives after the resend is not an edge case, it’s the median on a slow network.
- Show the timer state, not a disabled button.
Resend in 23sis information. A greyed-out button is punishment. The user wants to know how long they’re meant to wait, not be told to stop trying. - Voice call as a second channel. Same code, different carrier path. Some networks block short codes, some users have a flaky SMS inbox, some are on a number that can’t receive texts. The voice fallback is one tap and uses 90% of the same code-delivery infrastructure.
- “Didn’t get the code?” never dead-ends. No path through that link ends at “contact support.” It ends at a working alternative: voice, email, a different number. Support is for the case where every alternative also failed. Most users don’t get there.
- Lockouts are short, visible, and reversible. “Too many attempts” with no time signal is a wall. “Try again in 60 seconds” is a delay. The user accepts a delay. They abandon a wall.
What it costs when you get this wrong
On that KYC product, OTP failure was the biggest preventable drop-off in the verification funnel. Bigger than form length. Bigger than camera errors on the ID photo. Bigger than the wait for the verification itself.
The fix wasn’t a single feature. It was the five rules above, shipped over a few weeks. The screen still had a six-digit input and a resend timer. Nobody walked away saying “the OTP was great.” They walked away because nothing told them to give up.
The work was invisible to anyone except the people building it. That’s why it’s the work most teams skip.
The loneliest screen in your product
OTP is treated like a backend concern. SMS provider, code length, expiry window. The screen itself is a placeholder, a thing the engineer puts together in an afternoon because nobody’s asking for it.
That’s why it’s where products lose the most users.
No context. No help. A single input box and a ticking timer. The user is one bad signal away from giving up. Design this screen like you designed onboarding.
The slowest moment in your product is also the most important.