Sendexa LogoDocs

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

Cooldown

60s

Between resends

Max Resends

3

Per hour per phone

Expiry Reset

Fresh

New timer starts

Attempts

Reset

Counter starts fresh

POST
/v1/otp/resend
Retry

Request Body

application/json
JSON
{
"phone": "0555539152",
"id": "otp_123456789_abc"
}

Response

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

1

User didn't receive code

Action

Resend with phone only

Result

New code sent, old invalidated

2

Code expired

Action

Resend with phone or ID

Result

Fresh code with new expiry

3

Too many wrong attempts

Action

Resend required

Result

Reset attempt counter, new code

Cooldown Rules

ConditionCooldownHourly Limit
Initial request
None
3
First resend
60 seconds
3
Subsequent resends
60 seconds
3

Try It Yourself

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

Implementation Examples

JavaScript
// Resend OTP with countdown and error handling
class 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 cooldown
const 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 created
return {
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 number
if (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 updates
window.dispatchEvent(new CustomEvent('otpCooldown', {
detail: { remaining }
}));
}, 1000);
}
}
// Usage with React component
function 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 (
<button
onClick={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