Sendexa LogoDocs

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

High Security
HIGH

Advanced security measures for sensitive operations

  • PCI-DSS compliant
  • Encrypted storage
  • Automatic code expiration
  • Brute force protection
Default Expiry

5 min

Configurable 1-1440 min

Max Attempts

3

Per OTP by default

Cooldown

60s

Between requests

Code Length

4-12

Chars depending on type

POST
/v1/otp/request
Enhanced
Idempotent

Request Body

application/json
JSON
{
"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

TypeLengthSecurityUse CaseExample
NUMERIC
4-8 digits
Medium
General purpose, easiest to enter123456
ALPHANUMERIC
6-12 chars
High
Financial transactions, high-securityA7B9X2K4
ALPHABETIC
5-10 letters
Low-Medium
Voice-based verificationABCDEF

Expiry Configuration

minutes
Default: 5

Range: 1-60 minutes

Quick verifications

hours
Default: 1

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

per phone per hour

Max OTP requests

60 seconds

cooldown

Between requests to same phone

5 attempts

per OTP

Max verification attempts

Response

JSON
{
"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

POST
https://api.sendexa.co/v1/otp/request

Implementation Examples

JavaScript
// Complete OTP request flow with error handling
class 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 countdown
console.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 limiting
const 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-digits
const cleaned = phone.replace(/D/g, '');
// Convert to international format if needed
if (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 logic
async 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 verification
sessionStorage.setItem('otpId', result.otpId);
sessionStorage.setItem('expiresAt', result.expiresAt);
// Start countdown timer
startOTPTimer(result.expiresAt);
return { success: true };
} else if (result.requiresWait) {
// Show countdown for existing OTP
return {
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_EXISTS

Active OTP Found

Show countdown to user instead of requesting new OTP. Use remainingSeconds to display timer.

RATE_LIMIT_EXCEEDED

Rate Limit Hit

Implement exponential backoff. Use retryAfter value for countdown display.

INVALID_PHONE

Invalid Phone Number

Validate phone format client-side before sending to API.