If your app needs to confirm a user actually controls the phone number they signed up with, a one-time password (OTP) sent by SMS is the standard way to do it. This is the complete, working path: send a code, verify the code, know your user is real.
No SDK to install. Two REST calls, JSON in and out.
1. Send the code
Call /otp/send with the recipient's phone number. Lunyazi generates a 6-digit code, stores it, and delivers it by SMS.
Save otp_id. You'll need it for the next call, and it's how Lunyazi knows which code your user is trying to enter. expires_in is in seconds; codes are valid for 5 minutes.
2. Verify what the user types in
Once your user enters the code they received, send it along with the otp_id to /otp/verify:
The detail that trips people up
A wrong code is not an error. The request still returns 200 with {"success": true, "verified": false, "attempts_remaining": N}: success means the check ran, verified is the actual answer. Checking response.ok alone and assuming a 200 means the user is verified is the single most common mistake in a first integration.
3. Handle the real failure cases
Beyond a simply wrong code, four other outcomes are real and worth handling explicitly rather than showing a generic error:
| Status | Meaning | What to show the user |
|---|---|---|
| 404 | OTP not found or already expired from the record entirely | "Request a new code" |
| 410 (OTP expired) | The 5-minute window passed | "Code expired, request a new one" |
| 410 (OTP already used) | This code already verified successfully once | Shouldn't normally happen in a correct flow; treat as a bug if it does |
| 429 | Too many wrong attempts on this code | "Too many tries, request a new code" |
Production considerations
- Resend cooldown: don't let a user request a new code every second. Add a short client-side cooldown on the "resend" button; the API itself also applies its own rate limiting per account.
- Email channel too: set
channel: "email"instead of"sms"andrecipientto an email address if you'd rather deliver the code by email for a given flow. - Test before going live: Lunyazi's fixed test numbers return a known code without charging real credits or reaching a real carrier, so you can verify your whole flow before spending anything.
- Don't log the code. Treat the OTP itself the same as a password in your own logs: never write it to application logs or error-tracking tools.
Why OTP, and why it matters for compliance
Under Eswatini's Data Protection Act 2022, Section 14 requires businesses to implement appropriate technical measures to protect personal data. OTP verification is one of the most direct ways to satisfy that requirement for anything involving account access, password resets, or confirming a transaction actually came from the account holder.
Frequently Asked Questions
How long is an OTP code valid for?
5 minutes from when /otp/send is called, returned in the response as expires_in (in seconds).
What happens if the user enters the wrong code?
The request still returns 200 with verified: false and an attempts_remaining count. It's not an error status. Check the verified field specifically, not just whether the request succeeded.
Can I send the OTP by email instead of SMS?
Yes. Set channel: "email" and recipient to an email address in the /otp/send call. The rest of the flow is identical.
Do I need a separate SDK for OTP versus regular SMS?
No. Both use the same plain REST API: /otp/send and /otp/verify are just two more endpoints on the same account and API key as SMS sending.
How many attempts does a user get to enter the correct code?
A limited number before the OTP is locked out with a 429 response. At that point the correct flow is to have the user request a fresh code, not keep retrying the old one.
Ready to add OTP to your app?
Free to start, no credit card required. Get an API key and send your first verification code in the next five minutes.
Get your API key →