Send and verify OTPs in India with one API
Two calls — send and verify — and the code generation, delivery, expiry, and retry logic are handled for you. Auto-read fills the code on Android, voice fallback covers a failed SMS, and OTPs reach users around the clock as service traffic. You never store a code.
What you can build
OTPs that arrive
Verify without storing codes
Service traffic, any hour
Send your first OTP in five minutes
A verification is two calls. otp/send generates a code, delivers it, and returns a verification_id. When the user types the code, otp/verify checks it against that ID and returns the result.
The code never comes back to you, so it stays out of your logs and your database. You decide the length, the time-to-live, and how many attempts a code allows; we enforce them. A resend reuses the same verification so a user can ask for the code again without starting over.
Turn on auto-read by including your app hash, and Android fills the code in for the user. Turn on voice fallback, and a code that doesn't arrive by SMS is read out on a call — same verification ID, same verify call, so your code path doesn't branch.
Errors are explicit: a wrong code returns a mismatched status with the attempts remaining, an expired code says so, and a number that has requested too many codes is rate-limited with a retry hint. You branch on the status rather than guessing.
Both send and verify emit webhook events — delivered, failed, verified, and expired — so a backend that prefers events over polling reacts the moment a code is confirmed. The verification_id ties every event back to the attempt it belongs to, and a frontend can move the user forward on the verified event without a round-trip of its own.
Test the whole flow with a test key before you go live. Test sends are accepted and verified without touching the operator network, so you wire up send, verify, the webhook, and your retry interface end to end, then switch to a live key once the template is approved. Nothing about the calls changes between test and live except the key.
A resend reuses the verification rather than starting a new one, so a user who asks for the code again doesn't invalidate the first one mid-flight and your attempt counter stays meaningful. You set how soon a resend is allowed and how many a single number gets, which is usually enough to keep the resend button from becoming a way to pump traffic.
The same verify call works whether the code arrived by SMS or by voice, so one code path covers both channels. When you need to debug, the per-attempt log shows the channel, the status at each step, and the time, so a stuck verification is something you can see rather than reproduce.
# 1. Send an OTP — we generate and store the code
curl https://api.smsapi.in/v1/otp/send \
-H "Authorization: Bearer $SMSAPI_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "+919876543210",
"template_id": "1707170000000000000",
"dlt_entity_id": "1101400000000000000" }'
# -> { "verification_id": "vrf_8a21", "status": "sent" }
# 2. Verify the code the user typed
curl https://api.smsapi.in/v1/otp/verify \
-H "Authorization: Bearer $SMSAPI_KEY" \
-d '{ "verification_id": "vrf_8a21", "code": "481920" }'
# -> { "verification_id": "vrf_8a21", "status": "verified" }API request shapes are representative pending the white-label telco API spec (FLAGS_AND_ASSUMPTIONS.md).
What’s included
Numeric or alphanumeric
Four to eight digits, or alphanumeric, generated and validated for you.
One verify endpoint
Submit the user's input; get verified, expired, or mismatched back.
SMS Retriever support
Include the app hash so Android can auto-fill the code without the user typing it.
Voice OTP fallback
If the SMS doesn't deliver, the same code is read out on a voice call.
TTL and resend
Set how long a code is valid and how soon a resend is allowed.
Attempt caps and rate limits
Cap verify attempts per code and sends per number to blunt brute force.
Delivery and verify events
Receipts and verification outcomes pushed to your webhook in real time.
Codes never returned to you
The code is delivered to the user, not to your logs — you only ever verify.
Built for Indian telecom compliance
OTP traffic is governed by TRAI and sent under the DLT framework, like all Indian SMS. What makes OTPs different is the category: a one-time passcode is service-implicit — a direct response to something the user just did — so it is exempt from DND scrubbing and time-of-day limits. A login code at midnight is allowed in a way a promotional message is not.
That exemption only holds if the template is registered in the right category. We register your principal entity, your sender header, and the OTP template, and we validate the entity and template IDs on every send so a miscategorised message fails fast rather than being scrubbed or delayed. The user's action is the consent, and we keep the per-message record that evidences it.
Voice fallback follows the same logic on the voice side: a service OTP call reaches the user without NDNC scrubbing because it is a response, not a campaign. Our DLT guide covers which category a given message belongs in and how the registration is done.
Because the code never leaves our side until it reaches the user, a leak in your own logs can't expose it, and the attempt caps and short time-to-live limit how useful an intercepted code would be. For audit, every send and verify is recorded against the verification_id with a timestamp — the evidence trail a security review or a chargeback dispute asks for, without you building a store for it.
Frequently asked
How does auto-read OTP work on Android?
Include your app's hash string in the request and format the message for Google's SMS Retriever API. The Android client picks up the code from the incoming SMS and fills it in without the user reading or typing anything, which removes the most common point of drop-off in a verification flow.
What happens if the SMS doesn't arrive?
You can enable voice fallback: if the SMS isn't delivered within a window you set, the same code is placed as a short voice call that reads it out. The verification_id stays the same, so your verify call doesn't change regardless of which channel delivered the code.
Do I need to store the OTP or its expiry?
No. We generate the code, deliver it, store it against a verification_id, and expire it on the TTL you set. You never see the code — you call verify with the user's input and the verification_id, and we return the result. That keeps codes out of your database and your logs.
Can I use alphanumeric codes or a custom length?
Yes. Choose four to eight digits, or alphanumeric, per template. The format has to match the DLT-registered template, so once a template is approved the length and character set are fixed for that template.
How do you stop OTP brute-force and abuse?
You can cap verify attempts per code, limit how many codes a single number can request in a window, and set a short TTL. Together those mean a guessed code expires before it can be brute-forced and a number can't be used to pump traffic.
Do OTPs reach users on DND at night?
Yes, when registered correctly. OTPs are service-implicit traffic under DLT, which is exempt from DND scrubbing and time-of-day rules, because it's a response to a user action rather than marketing. The template category you register decides this, so we set it up with you.
How are OTPs priced?
Per message, with GST shown separately, on the transactional route; a voice fallback is charged as a voice call only when it's used. See the pricing page for current rates.