An SMS code step looks simple, yet it hides timing, state and security rules that break in surprising ways. A written test plan keeps testers from checking only the happy path and turns tribal knowledge into a repeatable checklist.
Scope the flow first
Draw the states: number entry, code sent, code entry, verified, and the exits for resend, change number and abandon. Every arrow between states is a test. Note the timers too, such as code lifetime, resend cooldown and session expiry.
Happy path tests
- A valid number receives a code and entering it completes signup.
- The verified state persists after a page refresh and after logout and login.
- The correct country default appears and the formatted number is shown back to the user.
Timing tests
- Enter the code just before expiry and just after it. The boundary behaviour should match the specification.
- Click resend during the cooldown and confirm it is blocked with a clear message.
- Leave the page idle past the session timeout and confirm recovery is graceful.
- Verify that a late code arriving after expiry is rejected cleanly.
Negative tests
- Wrong code once, then correct. Confirm the attempt counter increments.
- Exceed the allowed attempts and confirm lockout and its duration.
- Reuse an already consumed code.
- Submit empty, short, long and non-numeric input.
- Use a code from a different session or a different number.
State and concurrency tests
Open the flow in two tabs and request codes from both. Only the latest code should work, or the rule should be clearly defined. Try changing the number after a code is sent and confirm the old code is invalidated.
Cross-cutting checks
- Accessibility: keyboard only completion, screen reader labels, visible focus.
- Mobile: numeric keypad appears, autofill works, rotation does not clear input.
- Localisation: messages and formats in each supported language.
- Security: no code in URLs or logs, rate limits on send and verify endpoints.
Reporting results
Record the environment, the build, the number type and the exact timestamps for each failure. Timestamps matter because delivery issues are often upstream, and support needs them to trace a message.
Run the full matrix before each release and a smoke subset after every deploy. The plan will pay for itself the first time it catches a boundary bug before customers do.
Frequently asked questions
How many test numbers do I need?
Enough to cover each country, number type and service you support, plus spares for retries. Reuse can distort results, so rotate numbers and record which ones each test run used. A small pool tracked in a spreadsheet is usually fine.
What should be automated?
Automate the logic that does not depend on real delivery: validation, state transitions, limits, expiry and error messages, using a mock. Keep a short manual or scheduled real-delivery check for the end to end path, since that is where upstream changes show up.
How do I test expiry without waiting?
Make lifetimes configurable and use short values in test builds, or inject a controllable clock. Test the exact boundary, one second before and one second after, because off by one errors are common in expiry checks.
How should we record defects found in OTP flows?
Include the build, the environment, the exact timestamps for request, receipt and submission, the number type and country, and the visible error text. Add the order id when there is one. These facts let engineers separate product bugs from upstream delivery problems within minutes instead of days.
Put it into practice
Start from the services page and pick the service and country you need. Developers can find endpoints and examples in the API documentation. Learn who we are on the about page, find troubleshooting steps in the help center, or send a question through the contact form. The blog has further reading. When you are set, open the verification dashboard.
Related reading: Building a Staging Environment for OTP Testing and Contract Testing a Verification API with Mock Servers.