Verify API
Deliver secure one-time passwords to any mobile number. Configurable length, expiry, and type — with automatic rate limiting and brute-force protection built in so you can focus on your product.
Multiple PIN Types
Choose NUMERIC, ALPHANUMERIC, or ALPHABETIC codes, 4–10 characters, to match your security requirements.
Configurable Expiry
Set expiry from 1 minute to 24 hours with automatic invalidation — no manual cleanup needed.
Built-in Rate Limiting
Automatic cooldown periods and max-attempt enforcement prevent brute force and OTP spam.
Metadata Support
Attach custom context (userId, sessionId, IP) to each OTP request for audit trails.
Resend & Cooldown
Resend OTPs after a configurable cooldown without creating duplicate active codes.
Encrypted at Rest
Codes are encrypted with AES-256-GCM (never stored in plaintext), scrubbed automatically after expiry, with brute-force protection built in.
10 min
Configurable 1–1440 min
3
Default (max 10)
30 s
Between resends
4–10
Chars (all PIN types)
Authentication
Request OTP
Call /v1/otp/request — Sendexa generates a secure code and sends it by SMS.
curl -X POST 'https://api.sendexa.co/v1/otp/request' \-H 'Content-Type: application/json' \-H 'Authorization: Basic YOUR_DASHBOARD_BASE64_TOKEN' \-d '{"phone": "0244123456","from": "MyBrand","message": "Your code is {code}. Valid for {amount} {duration}.","pinLength": 6,"pinType": "NUMERIC","expiry": { "amount": 5, "duration": "minutes" }}'
{"success": true,"data": {"id": "otp_abc123xyz","expiresAt": "2024-01-15T10:35:00.000Z"}}
User enters the code
Display an input in your UI. Store the idfrom step 1 — you'll need it for verification.
Verify the code
Call /v1/otp/verify with the OTP ID and the code the user entered.
curl -X POST 'https://api.sendexa.co/v1/otp/verify' \-H 'Content-Type: application/json' \-H 'Authorization: Basic YOUR_DASHBOARD_BASE64_TOKEN' \-d '{"id": "otp_abc123xyz","pin": "482910"}'
{"success": true,"message": "OTP verified successfully","data": { "verified": true }}
API Endpoints
/v1/otp/requestGenerate and send a new OTP to a phone number via SMS.
/v1/otp/request/voiceGenerate a new OTP and speak it over a voice call.
/v1/otp/request/emailGenerate a new OTP and email it — always available, no channel fee.
/v1/otp/request/whatsappGenerate a new OTP and send it via WhatsApp — requires your own connected WhatsApp Business Account with an approved template.
/v1/otp/verifyValidate a code submitted by the user against the active OTP.
/v1/otp/resendRe-send the same OTP (same channel) after the cooldown period has elapsed. Also available at /resend/voice, /resend/email, /resend/whatsapp.
| Type | Length | Example | Best For |
|---|---|---|---|
NUMERIC | 4–10 digits | 482910 | Login, general verification (easiest to type) |
ALPHANUMERIC | 4–10 chars | A7B9X2K4 | Financial transactions, high-security flows |
ALPHABETIC | 4–10 letters | KPTMRB | Voice-read verification codes |
ACTIVE_OTP_EXISTSAn unexpired OTP is already active for this phone. Show the remaining countdown to the user.
RATE_LIMIT_EXCEEDEDMax 3 OTP requests per phone per hour.
IP_RATE_LIMIT_EXCEEDEDThe same IP address requested OTPs for too many distinct phone numbers within an hour.
OTP_EXPIREDThe OTP has passed its expiry time. Prompt the user to request a new code.
INVALID_PINWrong code entered. Check attemptsRemaining — lock the flow at 0 to prevent brute force.
OTP_NOT_FOUNDThe OTP ID does not exist or was already verified. Redirect to request a new OTP.
WHATSAPP_NOT_CONFIGUREDThis business has no active WhatsApp Business Account with an approved authentication template.
Use these placeholders in the message field — they are replaced at send time:
{code}The generated OTP code (required)
{amount}The expiry amount (e.g. 5)
{duration}The expiry unit (minutes / hours)
Example: "Your code is {code}. Valid for {amount} {duration}." → "Your code is 482910. Valid for 10 minutes."
| Channel | Availability | Notes |
|---|---|---|
| SMS | Works out of the box | Sendexa's own provider accounts — no setup required. |
| Voice | Works out of the box | Same as SMS — no setup required. |
| Works out of the box | Sent from Sendexa's mailer — no channel fee. | |
| Opt-in per business | Requires your own connected WhatsApp Business Account with a Meta-APPROVED AUTHENTICATION template. Every WABA is business-owned — there is no shared/platform account. |
If SMS delivery is still pending 45 seconds after the request and the business has WhatsApp configured, the same code is automatically also sent via WhatsApp — no extra request needed.
A flat $0.05 verification fee is charged once, only when a verification actually succeeds — not on every OTP request or resend. SMS and WhatsApp also incur a real per-message channel fee at send time (that part is charged regardless of whether the user ever completes verification, since the message still has to be delivered). Voice and Email have no separate channel fee.
| Channel | Price |
|---|---|
| SMS | $0.05 per successful verification + the real per-country SMS rate |
| $0.05 per successful verification + a per-message authentication-template fee | |
| Voice | $0.05 per successful verification — no separate channel fee |
| $0.05 per successful verification — no separate channel fee |
High-volume or enterprise pricing is negotiated directly — contact sales.
Pass an Idempotency-Key header on any /v1/otp/request* call. A retried request with the same key within 24 hours returns the original response instead of creating a second OTP — safe to use on network retries.
curl -X POST 'https://api.sendexa.co/v1/otp/request' \-H 'Content-Type: application/json' \-H 'Authorization: Basic YOUR_DASHBOARD_BASE64_TOKEN' \-H 'Idempotency-Key: 3f29-a91c-...' \-d '{ "phone": "0244123456", "from": "MyBrand" }'
Reserved test identifiers never send a real message and are never billed — the code is always 000000, on every channel:
1 555 000 0000–1 555 000 9999for SMS, Voice, and WhatsApp- Any address at
@sandbox.sendexa.testfor Email
Subscribe a webhook to any of these events from your dashboard. Every delivery is HMAC-SHA256 signed (x-webhook-signature) and wrapped in a stable envelope — { eventId, event, businessId, data, timestamp } — so you can dedupe retried deliveries on eventId. Failed deliveries are automatically retried with backoff (1 min, 5 min, 30 min) up to your webhook's configured max retries.
OTP_SENTOTP_DELIVEREDOTP_FAILEDOTP_VERIFIEDOTP_EXPIRED*889*142#When SMS is delayed or unavailable, users can dial *889*142# from any mobile phone to retrieve a pending OTP via USSD — no internet or SMS delivery required.
SMS delayed
Carrier congestion or network issues holding up delivery.
No data needed
Works on feature phones and areas with no internet access.
Same session
The OTP ID and expiry are unchanged — verify as normal.
The USSD fallback is active automatically for all OTP requests — no extra configuration required. The code is available until the session expires or the OTP is verified.
Security Best Practices
- Set
maxAmountOfValidationRetriesto 3–5 to prevent brute force attacks - Use 5-minute expiry for most flows — shorter is more secure
- Lock the UI after the attempt limit is reached; do not allow silent retries
- Pass the end-user's real
ipAddresson every request — it powers the IP-based velocity check, not just audit trails - Send an
Idempotency-Keyheader on every request so network retries never create a duplicate OTP - Never log or expose the OTP code in your application logs
- Verify server-side — never trust client-side OTP validation alone
- Build and test against the sandbox range before going live