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 (4-8 digits), ALPHANUMERIC (6-12 chars), or ALPHABETIC (5-10 letters)
Flexible Expiry
Custom expiry times from 1 minute to 24 hours with automatic invalidation
Rate Limiting
Built-in cooldown periods to prevent OTP spam and abuse
Metadata Support
Attach custom data (userId, sessionId) for seamless integration
Advanced security measures for sensitive operations
- PCI-DSS compliant
- Encrypted storage
- Automatic code expiration
- Brute force protection
Authentication Required
Enhanced OTP Features
- Template placeholders for personalized messages
- Automatic retry limiting to prevent brute force
- Configurable cooldown periods
- Webhook support for delivery status
5 min
Configurable 1-1440 min
3
Per OTP by default
60s
Between requests
4-12
Chars depending on 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-8 digits | Medium | General purpose, easiest to enter | 123456 |
ALPHANUMERIC | 6-12 chars | High | Financial transactions, high-security | A7B9X2K4 |
ALPHABETIC | 5-10 letters | Low-Medium | Voice-based verification | ABCDEF |
Expiry Configuration
Range: 1-60 minutes
Quick verifications
Range: 1-24 hours
Extended sessions
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
Max OTP requests
60 seconds
Between requests to same phone
5 attempts
Max verification attempts
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) {throw new OTPError(data.message, data.error?.code, data.error?.details);}return {success: true,otpId: data.data.id,expiresAt: data.data.expiry.expiresAt,...data.data};} catch (error) {if (error.code === 'ACTIVE_OTP_EXISTS') {// Handle existing OTP - maybe show countdownconsole.log(`OTP expires in ${error.details.remainingSeconds} seconds`);return {success: false,requiresWait: true,remainingSeconds: error.details.remainingSeconds};}if (error.code === 'RATE_LIMIT_EXCEEDED') {// Handle rate limitingconst resetDate = new Date(error.details.resetAt);console.log(`Rate limited until ${resetDate.toLocaleTimeString()}`);return {success: false,rateLimited: true,resetAt: error.details.resetAt};}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, details) {super(message);this.code = code;this.details = details;}}// 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.requiresWait) {// Show countdown for existing OTPreturn {success: false,message: `Please wait ${result.remainingSeconds} seconds`};} else if (result.rateLimited) {return {success: false,message: `Too many attempts. Try again after ${new Date(result.resetAt).toLocaleTimeString()}`};}}
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
Active OTP Found
Show countdown to user instead of requesting new OTP. Use remainingSeconds to display timer.
Rate Limit Hit
Implement exponential backoff. Use retryAfter value for countdown display.
Invalid Phone Number
Validate phone format client-side before sending to API.