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
/verify call, not on resend itself.Resend Behavior
phoneis required;idis optional and targets a specific pending OTP for that phone- Reuses the original PIN length, type, and expiry duration
- 30-second cooldown between resends for the same phone/id
- Verification attempt counter resets for the new code
- Counts toward the same 3-per-hour phone rate limit as fresh requests
More Than SMS
POST /v1/otp/resend (SMS). The same shape is also available at /resend/voice, /resend/whatsapp, and /resend/email (which takes an email address in the phone field).30s
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 | 30 seconds | 3 |
| Subsequent resends | 30 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;}// phone is always required by the API; id is optional and only narrows// which pending OTP for that phone gets resent.async resendOTP(phone, id, options = {}) {try {const response = await fetch(`${this.baseUrl}/otp/resend`, {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': this.auth},body: JSON.stringify({ phone, id })});const data = await response.json();if (!response.ok) {// data.error is a flat string code — no nested details object,// no retryAfter. The 30s cooldown is a fixed, known constant.if (data.error === 'RESEND_COOLDOWN') {this.startCooldown(30, () => {if (options.onCooldownEnd) options.onCooldownEnd();});return {success: false,cooldown: true,message: data.message};}return {success: false,error: data.error || '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};}}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, undefined, {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 the 3-per-hour phone rate limit
Error Handling Tips
- RESEND_COOLDOWN: Show a fixed 30s countdown to the user (no retryAfter is returned)
- OTP_ALREADY_VERIFIED / OTP_BLOCKED / OTP_NOT_FOUND: Redirect to request a new OTP
- RATE_LIMIT_EXCEEDED: Stop resending — the phone has hit 3 requests/hour
- NETWORK_ERROR: Retry with increasing delays