OTP & 2FA via WhatsApp
Overview
WhatsApp authentication templates let you send OTP codes that arrive as rich messages with a one-tap copy button. Users don't need to manually read and type the code — they tap "Copy Code" and paste it into your app.
WhatsApp OTPs are delivered faster than SMS in most markets, cost less in many regions, and have near-100% open rates because users actively check WhatsApp throughout the day.
Why WhatsApp for OTP
| Factor | SMS | |
|---|---|---|
| Open rate | ~95% | ~99% |
| Delivery speed | 1-5 seconds | <1 second |
| Copy-code button | No (manual typing) | Yes (one-tap copy) |
| Sender identity | Number or alphanumeric | Verified business profile |
| Cost (EU) | ~€0.008 | ~€0.035 (auth template) |
| Cost (India) | ~€0.005 | ~€0.003 (auth template) |
| Requires app | No | Yes (WhatsApp installed) |
Prerequisites
- A WhatsApp Business Account connected to DIDfarm (setup guide)
- An approved authentication template in your Meta WABA
- A DIDfarm API key (Dashboard → API Keys)
- An SMS-enabled number for fallback (optional but recommended)
Step 1 — Create an Authentication Template
Go to Meta Business Suite → WhatsApp Manager → Message Templates → Create Template.
- Category: Authentication
- Template name:
login_otp - Language: English (add more as needed)
Meta provides a standard authentication template format. The body is auto-generated:
1 is your verification code. Security: Do not share this code.
Where 1 is replaced with your OTP code at send time. You can optionally add:
- Copy code button — adds a "Copy Code" button that copies the OTP to clipboard
- Code expiry — shows "This code expires in X minutes" below the message
Step 2 — Send OTP via WhatsApp
const code = crypto.randomInt(100000, 999999).toString();
// Store code with 5-minute expiry
await redis.setex(`otp:${phoneNumber}`, 300, code);
// Send via WhatsApp
const response = await fetch('https://didfarm.com/api/v1/whatsapp/messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
account_id: 1, // Your WhatsApp account ID
to: '+31611398058',
type: 'template',
template: {
name: 'login_otp',
language: { code: 'en' },
components: [{
type: 'body',
parameters: [{ type: 'text', text: code }]
}, {
type: 'button',
sub_type: 'url',
index: 0,
parameters: [{ type: 'text', text: code }]
}]
}
}),
});
const data = await response.json();
console.log(data.id, data.status); // message ID, "accepted"import secrets, requests
code = str(secrets.randbelow(900000) + 100000)
redis_client.setex(f"otp:{phone_number}", 300, code)
resp = requests.post(
'https://didfarm.com/api/v1/whatsapp/messages',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
json={
'account_id': 1,
'to': '+31611398058',
'type': 'template',
'template': {
'name': 'login_otp',
'language': {'code': 'en'},
'components': [{
'type': 'body',
'parameters': [{'type': 'text', 'text': code}]
}, {
'type': 'button',
'sub_type': 'url',
'index': 0,
'parameters': [{'type': 'text', 'text': code}]
}]
}
},
)
print(resp.json()['id'], resp.json()['status'])Step 3 — Verify the Code
The verification logic is identical to SMS OTP — compare the user's input against the stored code in Redis or your database.
async function verifyOTP(phoneNumber, userInput) {
const stored = await redis.get(`otp:${phoneNumber}`);
if (!stored) return { valid: false, error: 'Code expired' };
if (stored !== userInput) {
const attempts = await redis.incr(`otp_attempts:${phoneNumber}`);
await redis.expire(`otp_attempts:${phoneNumber}`, 300);
if (attempts >= 5) {
await redis.del(`otp:${phoneNumber}`);
return { valid: false, error: 'Too many attempts' };
}
return { valid: false, error: 'Invalid code' };
}
await redis.del(`otp:${phoneNumber}`);
await redis.del(`otp_attempts:${phoneNumber}`);
return { valid: true };
}Copy Code Button
When you configure the authentication template with a copy code button, the user sees a "Copy Code" button below the message. Tapping it copies the OTP to their clipboard so they can paste it into your app without typing.
This significantly improves conversion rates — users don't mistype digits or switch between apps to manually copy.
SMS Retriever API equivalent for WhatsApp, the code can be auto-filled without any user interaction at all.
SMS Fallback
Not all users have WhatsApp installed. Implement a fallback to SMS if WhatsApp delivery isn't confirmed within 10 seconds.
async function sendOTP(phoneNumber, code) {
// 1. Try WhatsApp first
const waResponse = await sendWhatsAppOTP(phoneNumber, code);
// 2. Wait for delivery confirmation (max 10 seconds)
const delivered = await pollDeliveryStatus(waResponse.id, {
timeout: 10000,
interval: 2000,
});
if (delivered) {
return { channel: 'whatsapp', messageId: waResponse.id };
}
// 3. Fall back to SMS
const smsResponse = await fetch('https://didfarm.com/api/v1/sms/messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
from_number_id: 42,
to: phoneNumber,
body: `Your code is ${code}. Expires in 5 minutes.`,
}),
});
return { channel: 'sms', messageId: (await smsResponse.json()).id };
}Best Practices
- Use 6-digit codes — matches industry standard and works with copy-code button
- Set 5-minute expiry — long enough to switch apps, short enough to be secure
- Rate limit to 1 OTP per 60 seconds per number, 5 per hour
- Always implement SMS fallback for users without WhatsApp
- Track which channel delivered successfully to optimize future sends per user
- Use a dedicated number for OTPs — don't mix authentication and marketing on the same number
- Log the channel, delivery time, and verification result for fraud detection
FAQ
Is WhatsApp OTP cheaper than SMS?
It depends on the country. In India and Brazil, WhatsApp authentication templates are significantly cheaper than SMS. In the EU, SMS is often cheaper. Use the fallback pattern to optimize cost: try WhatsApp first in low-cost markets, SMS first in high-cost WhatsApp markets.
What happens if the user hasn't opted in?
Authentication templates are exempt from the usual marketing opt-in requirement. You can send OTP codes to any WhatsApp number without prior consent. However, the user must have WhatsApp installed and the number must be registered.
Can I customize the OTP message?
Authentication templates have a fixed format set by Meta. You can only customize the security disclaimer text and whether to include a copy-code button and expiry timer. The body format (1 is your verification code) cannot be changed.
How do I track delivery?
DIDfarm sends delivery status webhooks for WhatsApp messages: sent, delivered, read, and failed. Set up a webhook endpoint in your WhatsApp account settings to receive these updates.
Ready to integrate WhatsApp OTP?
Check the full WhatsApp setup guide for account connection and template management.