Resend OTP
Resend an OTP code while preserving the original configuration. Perfect for handling cases where users didn't receive the code or need a fresh one with the same settings.
Preserve Configuration
Resend uses the same PIN length, type, and expiry as original request
Reset Expiry
New OTP gets fresh expiry window, old OTP is automatically invalidated
Cooldown Enforcement
Respects same rate limits as initial request to prevent abuse
Attempt Tracking
Resets verification attempt counter, maintains original max attempts
Authentication Required
Resend Behavior
- Original OTP is automatically invalidated when resend succeeds
- Cooldown period applies between resends (60 seconds)
- Attempt counter resets for the new OTP
- Same rate limits apply (3 per hour per phone)
60s
Between resends
3
Per hour per phone
Fresh
New timer starts
Reset
Counter starts fresh
Request Body
{"phone": "0555539152","id": "otp_123456789_abc"}
Response
{"success": true,"message": "OTP resent successfully","data": {"id": "otp_123456790_xyz","originalId": "otp_123456789_abc","phone": "233555539152","pinLength": 6,"pinType": "NUMERIC","expiry": {"amount": 10,"duration": "minutes","expiresAt": "2024-01-15T10:45:00.000Z"},"maxValidationAttempts": 3,"attemptsUsed": 0,"createdAt": "2024-01-15T10:35:00.000Z"}}
Common Resend Scenarios
User didn't receive code
Action
Resend with phone only
Result
New code sent, old invalidated
Code expired
Action
Resend with phone or ID
Result
Fresh code with new expiry
Too many wrong attempts
Action
Resend required
Result
Reset attempt counter, new code
Cooldown Rules
| Condition | Cooldown | Hourly Limit |
|---|---|---|
| Initial request | None | 3 |
| First resend | 60 seconds | 3 |
| Subsequent resends | 60 seconds | 3 |
Try It Yourself
https://api.sendexa.co/v1/otp/resendImplementation Examples
// Resend OTP with countdown and error handlingclass OTPResendManager {constructor(apiKey, apiSecret) {this.auth = 'Basic ' + btoa(apiKey + ':' + apiSecret);this.baseUrl = 'https://api.sendexa.co/v1';this.cooldownTimer = null;}async resendOTP(identifier, options = {}) {const { phone, id, metadata } = this.parseIdentifier(identifier);try {const response = await fetch(`${this.baseUrl}/otp/resend`, {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': this.auth},body: JSON.stringify({phone,id,metadata})});const data = await response.json();if (!response.ok) {if (data.error?.code === 'COOLDOWN_ACTIVE') {// Handle cooldownconst retryAfter = data.error.details.retryAfter;this.startCooldown(retryAfter, () => {if (options.onCooldownEnd) options.onCooldownEnd();});return {success: false,cooldown: true,retryAfter,message: `Please wait ${retryAfter} seconds`};}return {success: false,error: data.error?.code || 'UNKNOWN_ERROR',message: data.message};}// Success - new OTP createdreturn {success: true,otpId: data.data.id,expiresAt: data.data.expiry.expiresAt,data: data.data};} catch (error) {return {success: false,error: 'NETWORK_ERROR',message: error.message};}}parseIdentifier(identifier) {if (typeof identifier === 'string') {// Check if it's an OTP ID or phone numberif (identifier.startsWith('otp_')) {return { id: identifier };} else {return { phone: identifier };}}return identifier; // Already an object with phone/id}startCooldown(seconds, onEnd) {if (this.cooldownTimer) {clearInterval(this.cooldownTimer);}let remaining = seconds;this.cooldownTimer = setInterval(() => {remaining--;if (remaining <= 0) {clearInterval(this.cooldownTimer);if (onEnd) onEnd();}// Dispatch event for UI updateswindow.dispatchEvent(new CustomEvent('otpCooldown', {detail: { remaining }}));}, 1000);}}// Usage with React componentfunction ResendButton({ phone, onResend }) {const [cooldown, setCooldown] = useState(0);const [loading, setLoading] = useState(false);const manager = useRef(new OTPResendManager('api_key', 'api_secret'));useEffect(() => {const handleCooldown = (e) => {setCooldown(e.detail.remaining);};window.addEventListener('otpCooldown', handleCooldown);return () => window.removeEventListener('otpCooldown', handleCooldown);}, []);const handleResend = async () => {setLoading(true);const result = await manager.current.resendOTP(phone, {onCooldownEnd: () => {setCooldown(0);}});setLoading(false);if (result.success) {onResend?.(result.otpId);toast.success('New code sent!');} else if (result.cooldown) {toast.info(result.message);} else {toast.error(result.message);}};return (<buttononClick={handleResend}disabled={loading || cooldown > 0}className="text-blue-600 disabled:opacity-50">{loading ? 'Sending...' :cooldown > 0 ? `Resend in ${cooldown}s` :'Resend Code'}</button>);}
Resend Best Practices
Do's
- ✓ Show cooldown timer in UI
- ✓ Disable resend button during cooldown
- ✓ Clear error messages on successful resend
- ✓ Reset verification input field
Don'ts
- ✗ Allow unlimited resend attempts
- ✗ Hide cooldown information from users
- ✗ Resend without invalidating old code
- ✗ Ignore rate limit headers
Error Handling Tips
- COOLDOWN_ACTIVE: Show countdown timer to user
- OTP_INVALID_STATE: Redirect to request new OTP
- RATE_LIMIT_EXCEEDED: Implement exponential backoff
- NETWORK_ERROR: Retry with increasing delays