Verify API
Stable

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.

Default Expiry

10 min

Configurable 1–1440 min

Max Attempts

3

Default (max 10)

Cooldown

30 s

Between resends

Code Length

4–10

Chars (all PIN types)

Quick Start — 3-Step OTP Flow
1

Request OTP

Call /v1/otp/request — Sendexa generates a secure code and sends it by SMS.

Bash
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" }
}'
JSON
{
"success": true,
"data": {
"id": "otp_abc123xyz",
"expiresAt": "2024-01-15T10:35:00.000Z"
}
}
2

User enters the code

Display an input in your UI. Store the idfrom step 1 — you'll need it for verification.

3

Verify the code

Call /v1/otp/verify with the OTP ID and the code the user entered.

Bash
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"
}'
JSON
{
"success": true,
"message": "OTP verified successfully",
"data": { "verified": true }
}

API Endpoints

PIN Types
TypeLengthExampleBest For
NUMERIC4–10 digits482910Login, general verification (easiest to type)
ALPHANUMERIC4–10 charsA7B9X2K4Financial transactions, high-security flows
ALPHABETIC4–10 lettersKPTMRBVoice-read verification codes
Common Errors
400ACTIVE_OTP_EXISTS

An unexpired OTP is already active for this phone. Show the remaining countdown to the user.

429RATE_LIMIT_EXCEEDED

Max 3 OTP requests per phone per hour.

429IP_RATE_LIMIT_EXCEEDED

The same IP address requested OTPs for too many distinct phone numbers within an hour.

400OTP_EXPIRED

The OTP has passed its expiry time. Prompt the user to request a new code.

400INVALID_PIN

Wrong code entered. Check attemptsRemaining — lock the flow at 0 to prevent brute force.

404OTP_NOT_FOUND

The OTP ID does not exist or was already verified. Redirect to request a new OTP.

400WHATSAPP_NOT_CONFIGURED

This business has no active WhatsApp Business Account with an approved authentication template.

Message Template Variables

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."

Channels
ChannelAvailabilityNotes
SMSWorks out of the boxSendexa's own provider accounts — no setup required.
VoiceWorks out of the boxSame as SMS — no setup required.
EmailWorks out of the boxSent from Sendexa's mailer — no channel fee.
WhatsAppOpt-in per businessRequires 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.

Pricing

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.

ChannelPrice
SMS$0.05 per successful verification + the real per-country SMS rate
WhatsApp$0.05 per successful verification + a per-message authentication-template fee
Voice$0.05 per successful verification — no separate channel fee
Email$0.05 per successful verification — no separate channel fee

High-volume or enterprise pricing is negotiated directly — contact sales.

Idempotency

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.

Bash
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" }'
Sandbox / Test Mode

Reserved test identifiers never send a real message and are never billed — the code is always 000000, on every channel:

  • 1 555 000 00001 555 000 9999 for SMS, Voice, and WhatsApp
  • Any address at @sandbox.sendexa.test for Email
Webhooks

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
USSD Fallback — *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.

1User dials*889*142# on any mobile network
2Enter phone numberThe number the OTP was sent to
3Retrieve codeThe active OTP is displayed on screen
4Enter in your appUser types the code — verification works identically

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.