WhatsApp

WhatsApp OTP API Integration in India (2026): Implementation Guide

7 min read

This guide is for developers building WhatsApp OTP flows for Indian businesses. It covers the actual integration steps, real API formats, template requirements, delivery tracking, and fallback architecture — not a conceptual overview of why WhatsApp OTP exists.

whatsapp-otp-api-integration

If you need the business case first, how WhatsApp OTP service works in India covers pricing, DLT compliance, and channel comparison. Come back here when you're ready to build.

What You Need Before Starting

  • An approved WhatsApp Business Account (WABA) via a BSP — MessageBot or another official Meta Business Solution Provider

  • Your API token from the MessageBot dashboard

  • An approved WhatsApp Authentication template — not a utility or marketing template

  • The recipient's phone number in international format (91XXXXXXXXXX for India)

The authentication template has strict format requirements. It must have exactly one variable slot for the OTP code, an optional expiry line, and an optional one-tap autofill button. No promotional content, no hyperlinks, no emojis. Meta's review is typically fast for correctly formatted authentication templates — rejections almost always come from format violations.

How the Flow Works

User action → Backend generates OTP → Store in cache with TTL
→ API call to WhatsApp → Message delivered → User submits code
→ Backend validates → Session authenticated

OTP generation — do this right:

import secrets
import redis
import time
def generate_otp(phone_number: str, expiry_seconds: int = 300) -> str:
    # Cryptographically secure random OTP
    otp = str(secrets.randbelow(900000) + 100000)  # 6-digit OTP
    
    # Store in Redis with TTL — not in relational database
    r = redis.Redis()
    key = f"otp:{phone_number}"
    r.setex(key, expiry_seconds, otp)   
    return otp
def verify_otp(phone_number: str, submitted_otp: str) -> bool:
    r = redis.Redis()
    key = f"otp:{phone_number}"
    stored_otp = r.get(key)
    if not stored_otp:
        return False  # Expired or never generated
    if stored_otp.decode() == submitted_otp:
        r.delete(key)  # Invalidate immediately after use
        return True
    return False

Use Redis or Memcached with a TTL — not a relational database. Storing OTPs in a database adds unnecessary latency and persistence risk. Invalidate the OTP immediately after successful verification, not after expiry.

Sending WhatsApp OTP via MessageBot API

MessageBot's WhatsApp API uses a REST endpoint. Here's the actual request format:

POST Request

POST https://api.messagebot.in/v1/whatsapp/message

Content-Type: application/json

Authorization: Bearer YOUR_API_TOKEN

{
  "to": "919876543210",
  "type": "template",
  "template": {
    "name": "otp_verification",
    "language": {
      "code": "en"
    },

    "components": [
      {
        "type": "body",
        "parameters": [
         {
            "type": "text",
           "text": "849201"
          }
        ]
      },
      {
       "type": "button",
        "sub_type": "url",
        "index": "0",
        "parameters": [
          {
            "type": "text",
            "text": "849201"
          }
        ]
      }
    ]
  }
}

The button component is the one-tap autofill button — it passes the OTP value to the app automatically when the user taps. Include it. It reduces manual entry errors and support tickets.

Successful response:

{
  "success": true,
 "messageId": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSM...",
 "status": "sent",
 "to": "919876543210"
}

Store the messageId — you need it to poll or receive delivery status.

Code Examples

Python

import requests
import json

def send_whatsapp_otp(phone_number: str, otp: str, api_token: str) -> dict:
    url = "https://api.messagebot.in/v1/whatsapp/message"
    
    payload = {
        "to": phone_number,
        "type": "template",
        "template": {
            "name": "otp_verification",
            "language": {"code": "en"},
            "components": [
                {
                    "type": "body",
                    "parameters": [{"type": "text", "text": otp}]
                },
                {
                    "type": "button",
                    "sub_type": "url",
                    "index": "0",
                    "parameters": [{"type": "text", "text": otp}]
                }
            ]
        }
    }
    
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_token}"
    }   
    response = requests.post(url, json=payload, headers=headers)
    return response.json()
# Usage
otp = generate_otp("919876543210")
result = send_whatsapp_otp("919876543210", otp, "YOUR_API_TOKEN")
message_id = result.get("messageId")

Node.js

const axios = require('axios');

async function sendWhatsAppOTP(phoneNumber, otp, apiToken) {
  const payload = {
    to: phoneNumber,
    type: 'template',
    template: {
      name: 'otp_verification',
      language: { code: 'en' },
      components: [
        {
          type: 'body',
          parameters: [{ type: 'text', text: otp }]
        },
        {
          type: 'button',
          sub_type: 'url',
          index: '0',
          parameters: [{ type: 'text', text: otp }]
        }
      ]
    }
  };

  const response = await axios.post(
    'https://api.messagebot.in/v1/whatsapp/message',
    payload,
    {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiToken}`
      }
    }
  );

  return response.data;
}

PHP

<?php
function sendWhatsAppOTP(string $phoneNumber, string $otp, string $apiToken): array {
    $payload = [
        'to' => $phoneNumber,
        'type' => 'template',
        'template' => [
            'name' => 'otp_verification',
            'language' => ['code' => 'en'],
            'components' => [
                [
                    'type' => 'body',
                    'parameters' => [['type' => 'text', 'text' => $otp]]
                ],
                [
                    'type' => 'button',
                    'sub_type' => 'url',
                    'index' => '0',
                    'parameters' => [['type' => 'text', 'text' => $otp]]
                ]
            ]
        ]
    ];

    $ch = curl_init('https://api.messagebot.in/v1/whatsapp/message');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        "Authorization: Bearer $apiToken"
    ]);

    $response = curl_exec($ch);
    curl_close($ch);
    
    return json_decode($response, true);
}

Tracking Delivery Status

WhatsApp delivery status comes via webhooks — configure your endpoint in the MessageBot dashboard.

Webhook payload structure:

{
  "messageId": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS...",
  "status": "delivered",
  "timestamp": "2026-06-30T10:22:04Z",
  "to": "919876543210"
}

Status values:

  • sent — message left your system

  • delivered — confirmed on recipient's device

  • read — recipient opened the message

  • failed — delivery failed

delivered is the status that matters for OTP retry logic. If you don't receive a delivered webhook within 30 seconds, trigger your fallback channel.

Webhook handler example (Node.js):

app.post('/webhook/whatsapp', (req, res) => {
  const { messageId, status, to } = req.body;
  
  if (status === 'delivered') {
    // Mark OTP as delivered in your session store
    markOTPDelivered(messageId);
  }
  
  if (status === 'failed') {
    // Trigger SMS fallback immediately
    triggerSMSFallback(to);
  }
  
  res.status(200).send('OK');
});

Building the Fallback

WhatsApp OTP will fail for some users — no internet, WhatsApp not installed, account issues. Build the fallback before you go live, not after users complain.

import asyncio

async def send_otp_with_fallback(phone_number: str, otp: str):
    # Attempt 1: WhatsApp OTP
    whatsapp_result = await send_whatsapp_otp(phone_number, otp)
    
    if not whatsapp_result.get('success'):
        # Immediate fallback to SMS if WhatsApp send fails
        return await send_sms_otp(phone_number, otp)
    
    # Wait 30 seconds for delivery confirmation via webhook
    delivered = await wait_for_delivery(
        whatsapp_result['messageId'], 
        timeout=30
    )
    
    if not delivered:
        # WhatsApp sent but not delivered — trigger SMS fallback
        await send_sms_otp(phone_number, otp)
    
    return whatsapp_result

The SMS fallback uses MessageBot's SMS API with messageType: 3 for the OTP route. How the SMS OTP API works in India — including the correct DLT parameters — covers the SMS side of this.

For high-security flows where both WhatsApp and SMS fail, Voice OTP is the final layer. The full omnichannel OTP architecture for India covers how to design the complete cascade.

Rate Limiting and Abuse Prevention

An OTP endpoint without rate limiting is a cost vector for bots and an account takeover risk. Minimum protections before going live:

from functools import wraps
import redis
r = redis.Redis()

def rate_limit_otp(max_requests: int = 3, window_seconds: int = 600):
    def decorator(func):
        @wraps(func)
        def wrapper(phone_number: str, *args, **kwargs):
            key = f"otp_rate:{phone_number}"
            current = r.incr(key)
            
            if current == 1:
                r.expire(key, window_seconds)
            
            if current > max_requests:
                raise Exception(f"Rate limit exceeded. Try again in {r.ttl(key)} seconds.")
            
            return func(phone_number, *args, **kwargs)
        return wrapper
    return decorator

@rate_limit_otp(max_requests=3, window_seconds=600)
def request_otp(phone_number: str):
    otp = generate_otp(phone_number)
    send_whatsapp_otp(phone_number, otp, API_TOKEN)

3 requests per number per 10 minutes is a reasonable default. Add exponential backoff on failed verification attempts — lock the account after 5 incorrect OTP submissions.

Authentication Template: What Meta Actually Approves

The template name in your API call must match exactly what's approved in your WhatsApp Business Manager. The approved template must be category: Authentication — not Utility or Marketing.

A correctly formatted authentication template:

{{1}} is your verification code. For your security, do not share this code.

With an optional expiry line:

{{1}} is your verification code. This code expires in 10 minutes.

Variables: {{1}} = the OTP value. That's the only variable allowed. Adding a second variable, a URL, or promotional language will get the template rejected or reclassified.

If your template was approved as Utility and you're using it for OTP delivery, you're being charged ₹0.115/message — same as authentication. But if Meta's systems reclassify it as Marketing, you'll see ₹0.8631/message charges. Check your template category in WhatsApp Business Manager.

Pre-Launch Checklist

  • Authentication template approved in WhatsApp Business Manager

  • Template category confirmed as Authentication (not Utility or Marketing)

  • API token stored in environment variables — never in source code

  • OTPs generated with secrets module (Python) or crypto (Node.js) — not random or Math.random()

  • OTPs stored in Redis/Memcached with TTL — not relational database

  • OTP invalidated immediately after successful verification

  • Rate limiting active — max 3 OTP requests per number per 10 minutes

  • Failed attempt counter with lockout after 5 wrong submissions

  • Webhook endpoint configured for delivery status

  • SMS fallback configured with 30-second trigger on non-delivery

  • Tested on actual devices — not just API response confirmation

  • One-tap autofill button included in template components

Common Errors and What They Mean

Error

Likely Cause

Fix

template_not_found

Template name mismatch

Verify exact template name in Business Manager

invalid_parameter

Wrong variable count or type

Check component structure matches approved template

recipient_not_on_whatsapp

User doesn't have WhatsApp

Trigger SMS fallback immediately

message_undeliverable

User blocked your number or account issue

Fallback to SMS, investigate account quality

rate_limit_exceeded

Too many messages in short window

Implement exponential backoff

Contact MessageBot support for WhatsApp API credentials, template submission, and endpoint reference specific to your account.

Share

Ready to Transform Your Business Communication?

Join thousands of Indian businesses already using MessageBot to engage customers effectively and drive business growth.

Free 100 SMS credits
24/7 support
TRAI compliant setup
No setup fees