Business Requirements
Your SaaS platform needs OTP verification for:
- User Registration
- Login Authentication
- Password Reset
- Email Change
- Mobile Number Change
- Transaction Approval
- 2FA Authentication
- Sensitive Account Settings Changes
Requirements:
- Multiple OTP providers
- Twilio
- MSG91
- AWS SNS
- SMTP Email
- WhatsApp API
- Enable/Disable providers
- Provider priority and failover
- OTP expiration
- Retry limits
- Rate limiting
- Prevent OTP reuse
- Detailed logs
- Audit trail
- Multi-tenant support (if SaaS)
High Level Architecture

Provider Manager decides:

Database Structure
otp_providers
Manage all platforms from admin panel.
| Field | Type |
|---|---|
| id | bigint |
| name | varchar |
| code | varchar |
| type | sms/email/whatsapp |
| is_active | tinyint |
| priority | int |
| config | json |
| created_at | timestamp |
Example
{
"api_key":"xxx",
"sender_id":"CODEEX"
}
otp_templates
Different OTP use cases.
otp_templates
| Field | Type |
|---|---|
| id | bigint |
| name | varchar |
| purpose | varchar |
| expiry_seconds | int |
| max_attempts | int |
| max_resend | int |
| is_active | tinyint |
Example
Login OTP Expiry: 300 sec Attempts: 5 Resend: 3
otp_requests
Main OTP table.
| Field | Type |
|---|---|
| id | bigint |
| user_id | bigint |
| tenant_id | bigint |
| otp_hash | varchar |
| purpose | varchar |
| recipient | varchar |
| provider_id | bigint |
| expires_at | timestamp |
| verified_at | timestamp |
| status | pending / verified / expired / blocked |
| resend_count | int |
| attempt_count | int |
| created_at | timestamp |
Important:
Never store OTP directly.
Store hash:
Hash::make($otp)
or
hash('sha256',$otp)
otp_logs
Track everything.
| Field | Type |
|---|---|
| id | bigint |
| otp_request_id | bigint |
| action | varchar |
| response | json |
| ip_address | varchar |
| created_at | timestamp |
Examples:
- OTP_GENERATED
- OTP_SENT
- OTP_RESENT
- OTP_VERIFIED
- OTP_EXPIRED
- OTP_BLOCKED
otp_rate_limits
Protection against abuse.
| Field | Type |
|---|---|
| id | bigint |
| recipient | varchar |
| purpose | varchar |
| total_requests | int |
| last_request_at | timestamp |
OTP Generation Flow

Before creating new OTP:
UPDATE otp_requests SET status='expired' WHERE recipient='9999999999' AND purpose='login' AND status='pending';
This ensures:
- Previous OTP becomes unusable
Only latest OTP works.
OTP Verification Logic
User enters OTP

Pseudo:
if(now() > expires_at)
{
return "OTP Expired";
}
if(attempt_count >= max_attempts)
{
status = 'blocked';
}
if(hash(inputOtp) != storedHash)
{
attempt_count++;
}
if(match)
{
status = 'verified';
verified_at = now();
}
Prevent OTP Reuse
After successful verification:
status = verified verified_at = now()
Future verification query:
SELECT * FROM otp_requests WHERE status='pending'
Verified OTP never matches again.
Additionally:
UNIQUE INDEX ( recipient, purpose, status )
Only one active OTP.
Resend Logic
Example:
Max Resend = 3
Track:
resend_count
Logic:
if(resend_count >= 3)
{
throw "Resend limit exceeded";
}
When resending:
Option 1 (Recommended)
Generate new OTP.
Old OTP Expired
New OTP Generated
More secure.
Attempt Limit Logic
Example:
5 Wrong Attempts
After that:
OTP Blocked
Update:
status='blocked'
User must request new OTP.
Rate Limiting
Prevent OTP bombing.
- Per Mobile – 5 OTP / 15 min
- Per IP – 10 OTP / hour
- Per User – 20 OTP / day
Store counters in:
- otp_rate_limits
- or Redis
Recommended:
Redis
for speed.
Provider Failover Logic
Priority table:
| Provider | Priority |
|---|---|
| Twilio | 1 |
| MSG91 | 2 |
| AWS SNS | 3 |
Algorithm:
foreach($providers as $provider)
{
try {
sendOtp();
break;
}
catch(Exception $e){
continue;
}
}
Logs:
- Twilio Failed
- MSG91 Success
Admin Panel Features
OTP Providers
✓ Twilio Active
✓ MSG91 Active
✗ AWS SNS Disabled
Change without deployment.
OTP Settings
Login OTP
Expiry: 5 Min
Attempts: 5
Resend: 3
Analytics Dashboard
Show:
- Total OTP Sent
- Success Rate
- Failed OTP
- Provider Wise Delivery
- Resend %
- Blocked Attempts
Total OTP Sent
Success Rate
Failed OTP
Provider Wise Delivery
Resend %
Blocked Attempts
Recommended Status Flow

Enterprise-Level Enhancements
Device Fingerprinting
Store:
- Device ID
- Browser
- OS
- Location
Detect suspicious requests.
OTP Purpose Isolation
Separate OTPs:
- LOGIN
- PASSWORD_RESET
- PAYMENT
- EMAIL_CHANGE
A login OTP should never verify a password reset request.
Queue-Based Delivery
Never send directly.
Using:
- Laravel Queue
- Redis
- RabbitMQ
- SQS
Recommended Final Design
Tables:
- otp_providers
- otp_templates
- otp_requests
- otp_logs
- otp_rate_limits
Security Rules:
- Hash OTP before storing
- Only latest OTP active
- Previous OTP automatically invalid
- Expiry configurable
- Retry configurable
- Resend configurable
- Provider failover
- Admin manageable
- Full audit logs
- Redis rate limiting
- Queue-based delivery
- Multi-tenant ready

