TL;DR: One-time-code verification looks finished after the happy path works, and the threads show it usually isn't. Across 182 high-scoring posts about OTP flows, two populations describe the same system from opposite ends: developers working out how to share a rate-limit budget across SMS and email without leaving a bypass, and end users writing app-store reviews about being locked out of accounts because the code never arrived. Both are symptoms of the same missing layer.
The evidence
PainHunt holds 182 posts scoring 10 or higher out of 15 that discuss one-time codes and verification flows, averaging 11.3/15 with a pain intensity of 8.1/10 — the highest intensity in this batch. The source split is the most useful part of the dataset: 91 from the App Store, 28 from developer-focused Discourse communities, plus Mastodon, Google Play, and Reddit. App-store reviews are locked-out users. Discourse threads are the engineers trying to prevent exactly that.
The developer side names specific failure modes.
Per-channel counters create a bypass. Email and SMS verification often maintain separate attempt counters, so a user who exhausts one channel simply switches to the other and gets a fresh budget. Whatever limit the design intended, the effective limit is the sum across channels — and an attacker reads that as free retries.
Concurrency corrupts state. Refreshes, duplicate form submissions, two open tabs, and a fallback click while a message is still in flight all race each other. The threads describe codes being invalidated by a resend the user triggered a second before the first one arrived, and counters that disagree with themselves depending on which request landed first.
Correct retry behaviour is more than a sleep. Limits must be shared across channels, honour Retry-After from the upstream provider, and avoid tight retry loops that amplify a provider hiccup into an outage. Hand-rolled implementations routinely miss at least one of the three.
Compliance widens the surface. Collecting phone numbers pulls in US and EU privacy constraints, country-specific policy handling, and requirements to keep verification logs without keeping the codes themselves. This is why teams that would happily use a library end up writing their own.
It is nearly untestable by hand. Late delivery, duplicate codes, channel switching, and recovery paths are all described as easy to miss before launch — which is consistent with the 91 app-store reviews on the other end.
Why now
Verification moved from an option to a default. Passwordless sign-in, phone verification for trust and safety, and step-up checks for sensitive actions mean many products now run this flow on every session rather than once at signup. A defect that used to affect registration now affects daily use.
Delivery got less reliable, not more. Carrier filtering, regional routing changes, and aggressive spam heuristics mean a correctly-sent code does not imply a received code. Systems designed when SMS delivery was assumed to work treat non-delivery as user error.
The cost of a lockout went up. When the same code gates payments, account recovery, and support identity checks, a failed verification is not a retry — it's a support ticket, a churned user, or an abandoned purchase. The app-store reviews in this dataset are that cost made visible.
The wedge
Building "an auth provider" is not the opportunity; that market is well served and the threads are from people who already decided against it. The opportunity is the verification layer for teams that keep their own identity system.
- Ship one budget keyed to the identity, not the channel. A single shared counter across SMS, email, voice, and authenticator fallback, with an explicit policy for what switching channels costs. This one decision closes the bypass every thread describes.
- Make concurrency the default assumption. Idempotent attempt handling, a single in-flight code per identity, and deterministic behaviour for the two-tabs case. Correctness under duplicate submission is the part teams cannot easily verify themselves, which is exactly what makes it worth buying.
- Instrument delivery, not just sending. Per-attempt delivery outcome, latency by carrier and region, and a rule that distinguishes "user typed the wrong code" from "the code never arrived." Without this split, the rate limiter punishes users for the provider's failure.
- Treat recovery as a first-class path. What happens after a user is locked out determines whether they come back. Grace paths, human-reviewable overrides, and an audit trail that satisfies compliance are the difference between a defensible product and a rate limiter.
- Sell the test suite as the demo. Late delivery, duplicate codes, channel switching, and clock skew, replayable on demand. Engineers evaluating this can't easily reproduce these cases, so showing them running is a stronger argument than any feature list.
Risks and honest caveats
- Auth vendors can add this in a quarter. Shared cross-channel limits are a plausible roadmap item for every managed identity provider. The defensible position is serving teams who cannot move to one of those providers, which is a narrower market than the raw thread count suggests.
- The buyer is diffuse. This sits between security, platform, and product. Problems with no single owner are hard to sell into even when everyone agrees they are real.
- The app-store half is not your customer. 91 of these threads are end users complaining about someone else's product. They are excellent evidence that the failure is widespread and expensive, and they will never buy anything. Treat them as market sizing, not as demand.
- Compliance obligations transfer to you. Handling phone numbers and verification logs across regions means inheriting retention rules and data-residency questions. That is a real cost of goods, not a footnote.
- Getting it wrong is worse than not shipping. A verification layer that miscounts locks real users out of real accounts. The failure mode of this product is the exact pain it sells against.
How to validate this further
The decisive question is whether teams see lockouts as an auth problem or a support problem, because the two buy from different budgets. Use the PainHunt dashboard to read the developer-side threads and the user-side reviews next to each other — the gap between how engineers describe the flow and how users describe being stuck is where the pitch lives. Then check whether a shared budget alone is worth paying for, or only the full recovery path is, using idea validation.
Related reading: account recovery for auth-locked paid users, JWT kid header hardening.