DEV Community

Nitin Suryawanshi
Nitin Suryawanshi

Posted on

Building a Production WhatsApp OTP Verification Service in Node.js

SMS has long been the default channel for One-Time Password (OTP) verification and two-factor authentication (2FA). However, SMS OTP delivery faces major systemic challenges today:

  • High Delivery Failure Rates: Telecom carrier filtering, SS7 vulnerabilities, and SIM-swap attacks.
  • High Latency: SMS OTPs frequently take 60 to 180 seconds to arrive in international regions, causing user registration abandonment.
  • Rising Carrier Costs: International A2P SMS rates continue to escalate globally.

By switching your authentication pipeline to WhatsApp Business Cloud API, OTPs arrive via encrypted push notifications in under 5 seconds with a 99.8% delivery success rate.

In this guide, we walk through building a resilient WhatsApp OTP authentication microservice using Node.js, Redis, and Meta's official Authentication template API.


Understanding Meta's Authentication Template Rules

Meta enforces strict security policies for Authentication conversations:

  1. Templates must contain only an alphanumeric verification code and an optional one-tap autofill button (COPY_CODE or ONE_TAP).
  2. URLs, images, media, and marketing text are strictly prohibited within Authentication templates.
  3. Pricing is significantly discounted: Meta prices Authentication conversations substantially lower than marketing broadcasts (see exact regional pricing in the Meta Per-Message Cost Guide).

Step 1: Generating and Storing the OTP in Redis

import crypto from "crypto";
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);

export async function generateOtp(phoneNumber) {
  // Generate a cryptographically secure 6-digit numeric code
  const code = crypto.randomInt(100000, 999999).toString();

  // Store in Redis with a 5-minute (300 seconds) expiration window
  const otpKey = `otp:${phoneNumber}`;
  await redis.set(otpKey, code, "EX", 300);

  return code;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Dispatching the WhatsApp Authentication Template

When sending an OTP, use Meta's standardized authentication template payload:

import axios from "axios";

export async function sendWhatsAppOtp(recipientPhone, otpCode) {
  const url = `https://graph.facebook.com/v21.0/${process.env.WHATSAPP_PHONE_ID}/messages`;

  const payload = {
    messaging_product: "whatsapp",
    recipient_type: "individual",
    to: recipientPhone,
    type: "template",
    template: {
      name: "auth_otp_code",
      language: { code: "en_US" },
      components: [
        {
          type: "body",
          parameters: [
            { type: "text", text: otpCode } // Injects OTP into the template body
          ]
        },
        {
          type: "button",
          sub_type: "url",
          index: "0",
          parameters: [
            { type: "text", text: otpCode } // Enables 1-tap "Copy Code" button
          ]
        }
      ]
    }
  };

  const response = await axios.post(url, payload, {
    headers: {
      Authorization: `Bearer ${process.env.WHATSAPP_ACCESS_TOKEN}`,
      "Content-Type": "application/json"
    }
  });

  return response.data;
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Verifying the User's Submitted Code

export async function verifyOtp(phoneNumber, submittedCode) {
  const otpKey = `otp:${phoneNumber}`;
  const storedCode = await redis.get(otpKey);

  if (!storedCode) {
    return { success: false, message: "OTP has expired or does not exist." };
  }

  // Constant-time string comparison to prevent timing attacks
  const isValid = crypto.timingSafeEqual(Buffer.from(storedCode), Buffer.from(submittedCode));

  if (isValid) {
    // Delete immediately after successful verification (single-use token)
    await redis.del(otpKey);
    return { success: true, message: "Phone number verified successfully!" };
  }

  return { success: false, message: "Invalid verification code." };
}
Enter fullscreen mode Exit fullscreen mode

Enterprise Authentication with AI Soul

If you don't want to manage WhatsApp template approvals, Redis clusters, and carrier delivery status callbacks manually, modern platforms like AI Soul provide a complete messaging layer.

AI Soul connects directly to Meta Cloud API, enabling automated WhatsApp Drip Campaigns, transactional order alerts, and customer notifications with 0% platform markup on Meta's base message rates.


Summary Checklist

  • [x] Use cryptographically secure numeric generation (crypto.randomInt).
  • [x] Expire OTPs in Redis after 3 to 5 minutes.
  • [x] Invalidate tokens immediately upon verification.
  • [x] Include a one-tap "Copy Code" button to maximize mobile login conversion.

Top comments (0)