Request OTP
Generate and send secure one-time passwords with configurable length, type, expiry, and automatic rate limiting. Perfect for authentication, transaction verification, and account recovery flows.
Multiple PIN Types
NUMERIC, ALPHANUMERIC, or ALPHABETIC codes, 4-10 characters long
Flexible Expiry
Custom expiry from 1 minute up to 24 hours with automatic invalidation
Rate Limiting
Phone-based and IP-based limits to prevent OTP spam and abuse
Metadata Support
Attach custom data (userId, sessionId) for seamless integration
Advanced security measures for sensitive operations
- Encrypted at rest (AES-256-GCM)
- Automatic code expiration
- Brute force protection
- Idempotency-Key support
Authentication Required
Enhanced OTP Features
- Template placeholders for personalized messages
- Automatic retry limiting to prevent brute force
- Idempotency-Key header to safely retry a request
- Sandbox/test-mode numbers for safe development
- Webhook support for send/delivery status
More Than SMS
POST /v1/otp/request (SMS). The same request/response shape is also available at /request/voice, /request/whatsapp, and /request/email — see Channels for the per-channel differences (Email uses the phone field for the email address; WhatsApp requires the business to have an approved authentication template, or returns WHATSAPP_NOT_CONFIGURED).10 min
Configurable 1-1440 (minutes or hours)
3
Per OTP by default
3 / hr
Per phone number
4-10
Chars, any PIN type
Request Body
{"phone": "0555539152","from": "YourBrand","message": "Your verification code is {code}. Valid for {amount} {duration}.","pinLength": 6,"pinType": "NUMERIC","expiry": {"amount": 10,"duration": "minutes"},"maxAmountOfValidationRetries": 3,"metadata": {"userId": "usr_12345","action": "login"},"ipAddress": "192.168.1.1"}
PIN Type Comparison
| Type | Length | Security | Use Case | Example |
|---|---|---|---|---|
NUMERIC | 4-10 digits | Medium | General purpose, easiest to enter | 123456 |
ALPHANUMERIC | 4-10 chars | High | Financial transactions, high-security | A7B9X2K4 |
ALPHABETIC | 4-10 letters | Low-Medium | Voice-based verification | ABCDEF |
Expiry Configuration
Range: 1-1440 minutes
Quick verifications
Range: 1-1440 hours
Extended sessions (pass duration: "hours")
Message Template Variables
{code}Generated OTP Code
Required placeholder - will be replaced with actual PIN
{amount}Expiry Amount
Numeric value from expiry.amount
{duration}Expiry Duration
"minutes" or "hours" from expiry.duration
Rate Limits & Security
3 requests
Counts requests + resends together
30 seconds
Applies to /resend, not the first request
1-10 attempts
Configurable, defaults to 3
Response
{"success": true,"message": "OTP sent successfully","data": {"id": "otp_123456789_abc","phone": "233555539152","pinLength": 6,"pinType": "NUMERIC","expiry": {"amount": 10,"duration": "minutes","expiresAt": "2024-01-15T10:40:00.000Z"},"maxValidationAttempts": 3,"createdAt": "2024-01-15T10:30:00.000Z","metadata": {"userId": "usr_12345"}}}
Try It Yourself
https://api.sendexa.co/v1/otp/requestImplementation Examples
// Complete OTP request flow with error handlingclass OTPManager {constructor(apiKey, apiSecret) {this.auth = 'Basic ' + btoa(apiKey + ':' + apiSecret);this.baseUrl = 'https://api.sendexa.co/v1';}async requestOTP(phone, options = {}) {const {from = 'YourBrand',message = 'Your verification code is {code}',pinLength = 6,pinType = 'NUMERIC',expiry = { amount: 10, duration: 'minutes' },maxAttempts = 3,metadata = {}} = options;try {const response = await fetch(`${this.baseUrl}/otp/request`, {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': this.auth},body: JSON.stringify({phone: this.formatPhone(phone),from,message,pinLength,pinType,expiry,maxAmountOfValidationRetries: maxAttempts,metadata})});const data = await response.json();if (!response.ok) {// data.error is a flat string code, e.g. "RATE_LIMIT_EXCEEDED" — no nested details object.throw new OTPError(data.message, data.error);}return {success: true,otpId: data.data.id,expiresAt: data.data.expiry.expiresAt,...data.data};} catch (error) {if (error.code === 'RATE_LIMIT_EXCEEDED' || error.code === 'IP_RATE_LIMIT_EXCEEDED') {// Fixed rolling-hour window — no retryAfter/resetAt is returned, so// just surface the message and let the caller retry later.return {success: false,rateLimited: true,message: error.message};}// Note: there's no "active OTP already exists" error — a new request// silently invalidates any still-pending OTP for that phone.throw error;}}formatPhone(phone) {// Remove any non-digitsconst cleaned = phone.replace(/D/g, '');// Convert to international format if neededif (cleaned.startsWith('0')) {return '233' + cleaned.substring(1);}return cleaned;}}class OTPError extends Error {constructor(message, code) {super(message);this.code = code;}}// Usage with retry logicasync function initiateLogin(phone) {const otpManager = new OTPManager('api_key', 'api_secret');const result = await otpManager.requestOTP(phone, {from: 'MyApp',pinLength: 6,expiry: { amount: 5, duration: 'minutes' },metadata: { action: 'login' }});if (result.success) {// Store OTP ID for verificationsessionStorage.setItem('otpId', result.otpId);sessionStorage.setItem('expiresAt', result.expiresAt);// Start countdown timerstartOTPTimer(result.expiresAt);return { success: true };} else if (result.rateLimited) {return {success: false,message: result.message};}}
Security Best Practices
Rate Limiting
Implement exponential backoff and never allow more than 3 requests per hour
Short Expiry
Use 5-10 minute expiry for most use cases. Shorter is more secure.
Attempt Limiting
Set max attempts to 3-5 to prevent brute force attacks
Metadata Tracking
Store userId, IP, and device info in metadata for audit trails
Error Handling Guide
Phone Rate Limit Hit
3 requests/hour per phone number, no retryAfter is returned — surface the message and let the user retry later.
IP Abuse Signal
The same IP requested OTPs for 10+ distinct phone numbers in the last hour. Only triggers if you pass ipAddress in the request.
WhatsApp channel unavailable
Returned by /request/whatsapp when the business has no active WABA + approved authentication template. Fall back to SMS/Voice/Email.