WhatsApp OTP API
Send and verify one-time passwords (OTPs) via WhatsApp using OutreachAgent's Meta BSP direct connection. 98%+ delivery, ~1.5s average latency, with automatic SMS fallback via Twilio.
Overview
The OutreachAgent WhatsApp OTP API lets you authenticate users via WhatsApp instead of SMS. Your server makes a single API call; the user receives a WhatsApp message with a code; your server verifies it. No WABA setup required on your end — we handle all Meta infrastructure.
How it works
Prerequisites
- • Active OutreachAgent workspace with the WhatsApp module (m2_whatsapp) enabled
- • At least one approved Meta OTP template (we create a default one for you on signup)
- • An API key from Settings → WhatsApp OTP
- • Sufficient credit balance (1 credit per OTP send)
Authentication
All API requests use Bearer token authentication. Include your API key in the Authorization header:
Authorization: Bearer oat_YOUR_API_KEYoat_ and are scoped to your workspace. Never expose your API key in client-side code.Send OTP
/v1/whatsapp/otp/sendGenerates a cryptographically secure OTP and delivers it via WhatsApp to the specified phone number. Returns an otp_id used to verify the code later.
Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone | string | Yes | Phone number in E.164 format (+919876543210) |
| template | string | No | Meta-approved OTP template name. Defaults to otp_verification |
| length | integer | No | OTP length: 4, 6, or 8 digits. Defaults to 6 |
| expires_in | integer | No | Expiry in seconds (60–900). Defaults to 300 (5 min) |
| fallback_sms | boolean | No | If true and WA delivery fails, falls back to SMS (requires m3_sms module). Defaults to false |
| metadata | object | No | Custom key-value pairs stored with the OTP (e.g., user_id, session_id) |
curl -X POST https://api.outreachagent.com/v1/whatsapp/otp/send \
-H "Authorization: Bearer oat_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+919876543210",
"template": "otp_verification",
"length": 6,
"expires_in": 300,
"fallback_sms": true
}'{
"success": true,
"otp_id": "otp_01HXK7QZRN8V4M2P9D3Y5F6G7H",
"channel": "whatsapp",
"phone": "+919876543210",
"expires_at": "2026-06-02T19:17:32Z",
"expires_in": 300,
"message_id": "wamid.HBgMOTE5ODc2NTQz..."
}Verify OTP
/v1/whatsapp/otp/verifyVerifies a submitted OTP code against an existing otp_id. Each OTP can be verified at most 3 times before it is permanently invalidated.
Request Body Parameters
| otp_id | string | Yes | The otp_id returned from the /send call |
| code | string | Yes | The code submitted by the user |
curl -X POST https://api.outreachagent.com/v1/whatsapp/otp/verify \
-H "Authorization: Bearer oat_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"otp_id": "otp_01HXK7QZRN8V4M2P9D3Y5F6G7H",
"code": "482915"
}'{
"valid": true,
"otp_id": "otp_01HXK7QZRN8V4M2P9D3Y5F6G7H",
"phone": "+919876543210",
"verified_at": "2026-06-02T19:14:18Z",
"channel": "whatsapp"
}Check OTP Status
/v1/whatsapp/otp/:otp_idRetrieves the current status of an OTP. Useful for async verification flows or auditing. Status values: pending | delivered | verified | expired | failed
curl https://api.outreachagent.com/v1/whatsapp/otp/otp_01HXK7QZRN8V4M2P9D3Y5F6G7H \
-H "Authorization: Bearer oat_YOUR_API_KEY"{
"otp_id": "otp_01HXK7QZRN8V4M2P9D3Y5F6G7H",
"status": "verified",
"phone": "+919876543210",
"channel": "whatsapp",
"created_at": "2026-06-02T19:12:00Z",
"expires_at": "2026-06-02T19:17:00Z",
"verified_at": "2026-06-02T19:14:18Z",
"attempts": 1,
"fallback_used": false
}SDK Examples
Node.js / TypeScript
import OutreachAgent from '@outreachagent/sdk';
const client = new OutreachAgent({ apiKey: process.env.OA_API_KEY });
// Send OTP
const { otp_id, channel } = await client.otp.send({
phone: '+919876543210',
template: 'otp_verification',
length: 6,
expiresIn: 300,
fallbackSms: true,
});
console.log(`OTP sent via ${channel} — ID: ${otp_id}`);
// Verify OTP (when user submits the code)
const { valid } = await client.otp.verify({
otp_id,
code: userSubmittedCode,
});
if (valid) {
// ✅ User is verified — proceed
} else {
// ❌ Invalid code — show error
}Python
import outreachagent
client = outreachagent.Client(api_key=os.environ['OA_API_KEY'])
# Send OTP
result = client.otp.send(
phone='+919876543210',
template='otp_verification',
length=6,
expires_in=300,
fallback_sms=True
)
print(f"OTP sent via {result.channel} — ID: {result.otp_id}")
# Verify OTP
verification = client.otp.verify(
otp_id=result.otp_id,
code=user_submitted_code
)
if verification.valid:
# ✅ User is verified
passSDKs available for: Node.js, Python, PHP, Go, Ruby, Java. Install via npm i @outreachagent/sdk or pip install outreachagent. All SDKs are open-source on GitHub.
Webhooks
OutreachAgent sends webhook events to your endpoint for real-time OTP status updates. Configure your webhook URL in Settings → WhatsApp OTP.
Event Types
otp.sentOTP successfully sent to WhatsApp
otp.deliveredWhatsApp confirmed the message was delivered to device
otp.verifiedUser submitted the correct OTP code
otp.expiredOTP expired without being verified
otp.failedOTP delivery failed (and SMS fallback was not configured)
otp.sms_fallbackWhatsApp delivery failed — OTP was delivered via SMS fallback
{
"event": "otp.verified",
"timestamp": "2026-06-02T19:14:18Z",
"data": {
"otp_id": "otp_01HXK7QZRN8V4M2P9D3Y5F6G7H",
"phone": "+919876543210",
"channel": "whatsapp",
"verified_at": "2026-06-02T19:14:18Z"
}
}Error Codes
| HTTP | Error Code | Description |
|---|---|---|
| 400 | invalid_phone | Phone number is not in E.164 format |
| 400 | template_not_found | OTP template not found or not approved by Meta |
| 401 | invalid_api_key | API key is missing, invalid, or expired |
| 402 | insufficient_credits | Workspace does not have enough credits to send OTP |
| 403 | module_inactive | WhatsApp module (m2_whatsapp) is not active for this workspace |
| 404 | otp_not_found | OTP ID does not exist or belongs to a different workspace |
| 410 | otp_expired | OTP has expired (past expires_at timestamp) |
| 422 | otp_already_verified | OTP was already successfully verified |
| 429 | rate_limit_exceeded | Too many OTP requests — see rate limits section |
| 503 | meta_api_unavailable | Meta WhatsApp API is temporarily unavailable (SMS fallback attempted if configured) |
Rate Limits
10/s
OTP sends per second
Global per workspace
5
OTP sends per phone/hour
Anti-abuse limit
3
Verification attempts per OTP
Then OTP invalidated
Rate limit headers are returned on every response: X-RateLimit-Remaining, X-RateLimit-Reset. Enterprise plans have higher limits — contact sales.
FAQ
Do I need my own WhatsApp Business Account?
No. OutreachAgent is a Meta BSP — we provide the WABA infrastructure. Your customers receive messages from your branded sender name (configured in Settings).
How does SMS fallback work?
If WhatsApp delivery fails (user doesn't have WhatsApp, or Meta API timeout), and you have fallback_sms: true + the SMS module active, OutreachAgent automatically sends the same OTP via Twilio SMS. The API response will include channel: "sms" if fallback was used.
Is the OTP code generated on my server or OutreachAgent's?
OutreachAgent generates and stores the OTP securely. Your server only receives the otp_id. This is intentional — it prevents your server from needing to store sensitive codes.
What WhatsApp template is used?
OutreachAgent creates a default otp_verification template on your WABA during setup. It's pre-approved by Meta. You can create custom templates in Settings → WhatsApp OTP → Templates.
How much does each OTP cost?
Each OTP send deducts 1 credit from your workspace balance. Credits are included in your module plan. Check Settings → Billing for your current balance and per-credit price.
Can I use this for 2FA in my web/mobile app?
Yes — that's the primary use case. Your backend calls /send when a user triggers 2FA, and /verify when they submit the code. Never make these calls from client-side code.
Ready to integrate?
Get your API key and send your first WhatsApp OTP in under 5 minutes.