By use case

How to send OTP verification codes with the WhatsApp API

To send a verification code over WhatsApp, your system generates the code, stores only its hash with an expiry time and uses the WhatsApp API just to deliver the message. Your backend does the validation, with attempt limits and resend limits per number.

By D-API engineering team5 min read

Who does what in an OTP flow

A verification code proves that a person has access to a number at that moment. That applies to sign-up, passwordless login, second factor and confirming sensitive actions, such as an email change or a withdrawal. The temptation is to look for a ready-made endpoint that generates and validates the code. Do not make that depend on the delivery channel.

The right split of responsibilities is this:

StepOwner
Generate the code from a secure random sourceYour backend
Store the hash, the expiry and the attempt counterYour backend
Deliver the message to the user’s WhatsAppWhatsApp API
Compare the typed code and invalidate it after useYour backend
Block abuse by number, IP and accountYour backend

That way, switching channels, adding SMS as a fallback or changing providers does not touch your security logic. The WhatsApp API becomes a transport, which is its role here.

Full example in Node.js

The snippet below shows both ends: issue and verify. Storage is abstracted as db, which can be Redis with expiry or a table with a validity column.

import { randomInt, createHash, timingSafeEqual } from 'node:crypto'
import { DApi } from 'd-api-sdk'

const dapi = new DApi({ apiKey: process.env.DAPI_API_KEY! })
const hash = (code: string) => createHash('sha256').update(code).digest()

export async function issueCode(phone: string) {
  const code = randomInt(0, 1_000_000).toString().padStart(6, '0')
  await db.otp.upsert(phone, {
    hash: hash(code),
    expiresAt: Date.now() + 5 * 60_000,
    attempts: 0,
  })
  await dapi.messages.sendText({
    sessionId: 'app-verification',
    to: phone, // e.g. 14155550123
    text: `${code} is your App verification code. It expires in 5 minutes. Do not share it with anyone.`,
  })
}

export async function verifyCode(phone: string, typed: string) {
  const record = await db.otp.get(phone)
  if (!record || record.expiresAt < Date.now()) return false
  if (record.attempts >= 5) return false
  await db.otp.incrementAttempts(phone)
  const ok = timingSafeEqual(hash(typed), record.hash)
  if (ok) await db.otp.remove(phone) // single use
  return ok
}

Note that the plain-text code only exists in memory during the send. It never reaches the database or the logs. For SDK details, see the Node.js SDK and the WhatsApp API in Node.js guide.

Security checklist

  • Real randomness. Use crypto.randomInt or equivalent. No Math.random and no time-derived codes.
  • Hash, never plain text. If the database leaks, open codes become instant access.
  • Short validity and single use. Five to ten minutes, and the code dies on the first successful verification.
  • Attempt limit per code. Five wrong tries invalidate the code and require a new send.
  • Send limit per number. For example, one resend every 60 seconds and at most five per hour. This protects the user from spam and keeps your number from looking like a runaway bot.
  • Limits per IP and per account, to stop anyone trying to fire codes at thousands of different numbers.
  • No codes in logs. Log that the send happened, with the phone number masked, never the code.
  • Short, clear message: code first, then validity and the do-not-share warning.

Send speed and availability

A code that arrives late arrives after the user gave up. A few decisions help:

  • Use a dedicated session for verification, separate from support and campaigns. That way a problem on a marketing number does not block your users’ logins.
  • Send synchronously, with no queue in between. The user is looking at the screen, and the API response tells you right away if something went wrong.
  • Watch the connection.status event of the verification session and alert the team if it leaves connected.
  • Offer SMS as a fallback after a few seconds or when the send fails. D-API has an SMS API on the same account.

A number used only for codes, with volume that tracks real app usage, has a healthy sending profile. Even so, it is worth knowing how to avoid bans.

When to use the official authentication template

On Meta’s official API, verification codes have their own template category: authentication. The template must be approved before use, and Meta charges per message, with rates that vary by category and country. In return, the send comes from the company’s verified number.

The official API is worth considering when:

  • Code volume is very high and comes from users who have never talked to the company.
  • The industry is regulated and internal policy requires the official channel.
  • A verified brand on the sender matters for users to trust the code.

On D-API, an official connection sends the template through the messages/send/template route, with the code in bodyVariables. The rest of the application stays the same. See the official WhatsApp API (Meta Cloud API) and the comparison in official vs unofficial API. A verification code is a kind of transactional notification, with a stricter speed requirement. The 3-day free trial, no credit card, lets you test the whole flow with a real number.

Frequently asked questions

Does the WhatsApp API generate the verification code for me?
No, and that is a good thing. The code should be created and validated in your system, which knows the user and the expiry rule. The API only delivers the message with the code to the person’s WhatsApp.
Is WhatsApp safer than SMS for OTP?
WhatsApp has end-to-end encryption and does not depend on the mobile carrier, which removes some SMS risks, such as interception on the network. But the security of the flow still depends on how your system generates, stores and expires the code.
How many digits and how long should the code be valid?
Six numeric digits is the most common standard: easy to type and hard to guess when attempts are limited. For sign-up and login, five to ten minutes of validity is usually enough.
What if the user’s number is not on WhatsApp?
Have a fallback channel. D-API also sends SMS from the same account, so your system can offer the code by SMS when the WhatsApp send fails or when the user asks for it.
Do I need a template to send codes on the official WhatsApp API?
Yes. On the official API, code messages fall under the authentication category and need a template approved by Meta. On the unofficial API the code goes out as a regular text message, with no prior approval.

Try D-API's WhatsApp API

3-day trial with full access. No credit card, no lock-in.