Back to Blog
Guide

WhatsApp OTP API in India (2026): How It Works, Costs and Setup

How WhatsApp one-time passwords work in India on Meta's Cloud API, what each code costs (Meta ₹0.115 + PayPerWA ₹0.20 per OTP, with no subscription or DLT registration), how to send and verify codes with PayPerWA's two-call Verify API or with your own code, and when to keep SMS as a backup.

PayPerWA Team22 min read

Key Takeaways

  • A WhatsApp OTP must be sent with a Meta authentication template: the body text is fixed, links, media and emojis are not allowed, and the code can be at most 15 characters.
  • There is no subscription, setup fee or minimum monthly spend: in India each OTP costs Meta ₹0.115, billed to your WhatsApp Business account's payment method, plus PayPerWA ₹0.20 from your prepaid wallet, and verifying a code is free.
  • PayPerWA's Verify API takes two calls: POST /api/v1/otp/send generates, sends and expires the code, and POST /api/v1/otp/verify checks what the user typed, with single use and a 5-attempt limit built in.
  • WhatsApp OTPs need no DLT registration, only an approved WhatsApp Authentication template, and you can still send a code you generated yourself through POST /api/v1/messages/send.
  • PayPerWA sends WhatsApp only, so keep a tested SMS fallback of your own for users without WhatsApp, failed messages and WhatsApp account problems.

What is a WhatsApp OTP?

A WhatsApp OTP is a one-time password or verification code that your business sends to a user's WhatsApp instead of by SMS. On Meta's WhatsApp Business Platform it must go out as an authentication template, the template category Meta reserves for identity verification. Meta is direct about this: if your app offers codes over WhatsApp, you must use an authentication template, and marketing or utility templates cannot carry a one-time passcode.

Meta writes the message text and you supply only the code. With both optional lines switched on, an English authentication message looks roughly like this:

483920 is your verification code. For your security, do not share this code.
This code expires in 10 minutes.
[ Copy Code ]
  • Body: fixed preset text, "{{1}} is your verification code." in English, with Meta's own translations for other languages. You cannot write your own copy.
  • Security recommendation (optional): adds "For your security, do not share this code."
  • Expiry footer (optional): "This code expires in N minutes.", with N from 1 to 90. Leave it out and no expiry warning is shown.
  • OTP button: Copy Code on every phone, or one-tap and zero-tap autofill in Android apps. PayPerWA offers Copy Code only.

Two limits catch developers out: URLs, media and emojis are not allowed in authentication template content or parameters, and the code itself can be at most 15 characters. Users still know who sent it, because the message arrives in a chat with your business's WhatsApp number.

A WhatsApp OTP API is the HTTP interface your server calls to get a fresh code to the user. PayPerWA offers two ways in, both paid per OTP with no subscription. With the Verify API, PayPerWA generates, sends, expires and checks the code in two calls. With the general send endpoint, /api/v1/messages/send, you generate the code yourself and PayPerWA delivers it. For a field-by-field look at the template, see our WhatsApp authentication templates guide.

Why consider WhatsApp instead of SMS for OTPs

Per message, WhatsApp and SMS OTPs land in a similar range in India (the numbers are in the cost section below), and bulk SMS can cost less at high volume, so the per-code price on its own is not the strongest reason. The stronger reasons are about setup and operations.

  • No DLT registration. Every commercial SMS in India, OTPs included, must pass TRAI's DLT checks: a registered sender, header and content template and, since 11 December 2024, a registered message path. A message that fails a check is blocked by the operator, even if your provider's API accepted it. MSG91's own list of failure reasons includes several DLT causes, such as template not matched, header inactive or PE-TM chain not registered. A WhatsApp OTP uses none of this: no DLT entity, header or content template, and no operator scrubbing. Its gate is a WhatsApp Authentication template that you create once and Meta reviews.
  • Pay per OTP, nothing up front. On PayPerWA there is no subscription, setup fee or minimum monthly spend. Each OTP costs Meta ₹0.115 + PayPerWA ₹0.20 in India, and checking a code with the Verify API is free.
  • Less typing. The Copy Code button copies the code to the clipboard in one tap, ready to paste, and iPhones on iOS 26 or later can suggest numeric codes in the keyboard with no integration work. Meta also offers one-tap and zero-tap autofill for Android apps, but PayPerWA does not offer them.
  • Built-in protections. On the Cloud API, authentication messages go only to the user's primary WhatsApp device and are masked on linked devices such as WhatsApp Web. WhatsApp also disables forwarding of authentication messages.
  • Statuses per message. Each send returns a WhatsApp message ID, and sent, delivered and failed statuses (plus read, if the user's settings allow it) arrive against that ID.
  • No conversation needed first. Template messages can be delivered at any time, so the user does not have to message your business before you send a code.

WhatsApp is not a replacement for every SMS OTP. Some users have no WhatsApp on the number they give you. Meta's best-practice guidance suggests offering users a choice of channels, and its zero-tap guidance says not to make WhatsApp your default code delivery method. A practical setup is to offer WhatsApp next to SMS and fall back when a WhatsApp message fails. Our WhatsApp OTP vs SMS OTP comparison covers the trade-offs in detail.

No subscription. ₹0.20/msg + Meta's rate (or $0.004/msg on USD wallets).

Create free account →

How a WhatsApp OTP works end to end on the Cloud API

  1. You create the template and Meta approves it. Meta classifies a template as authentication only if it uses Meta's authentication template format, has a one-time password button (copy code or one-tap) and follows the content limits. Since 1 October 2024, marketing or utility templates that should have been authentication are not recategorised; after advance notice, Meta rejects them.
  2. The user asks for a code. Your screen confirms the number is the user's WhatsApp number and says the code will arrive on WhatsApp.
  3. A code is generated and stored only as a hash, with an expiry time. Meta does not create codes: with PayPerWA's Verify API, PayPerWA generates and stores it; if you send through the general endpoint, your server does.
  4. Your server sends the template. In Meta's raw Cloud API request the code appears twice, as the body parameter and as the button parameter (sub_type url, index 0), and the recipient must be a phone number. Through PayPerWA's Verify API you pass only the phone number; through /api/v1/messages/send you pass the code once. Either way, both places are filled for you.
  5. WhatsApp delivers it within the time-to-live (TTL). The default TTL for authentication templates is 10 minutes (30 days for templates created before 23 October 2024). A message not delivered in that time is dropped.
  6. Statuses come back. Meta reports delivered, read or failed, and PayPerWA passes these on to your webhook, along with its own sent event.
  7. The user copies or autofills the code and your app submits it to your server. With the Verify API, your server passes it to /api/v1/otp/verify, which checks expiry and attempts and uses the code up; on the do-it-yourself path, your server checks its own hash, expiry and attempt count, then deletes the record.
  8. You fall back if needed when a message fails or is not delivered after a wait you choose.

Opt-in still applies. Meta requires opt-in before you message anyone on WhatsApp, and there is no exception for authentication. Since the November 2024 policy update, opt-in can be general rather than WhatsApp-specific, as long as it names your business, makes clear the person is agreeing to hear from you and complies with local law. The screen where users choose WhatsApp and enter their number is a natural place for that wording. PayPerWA's own opt-out block applies only to Marketing and Utility templates, so a contact marked as opted out in your PayPerWA contacts still receives the codes they request.

Line up your timers. Meta recommends a TTL equal to or shorter than your code expiry. If the footer says 5 minutes but the TTL is the default 10, a message delayed by 8 minutes arrives carrying a dead code. Ten minutes for the footer, the TTL and your server-side expiry is a sensible start, and it matches NIST SP 800-63B-4, which treats an out-of-band code as invalid if it is not used within 10 minutes. The Verify API's expiry_minutes defaults to 10 (you can choose 1 to 30). PayPerWA does not set a custom TTL when it submits a template, so new templates created through it use Meta's 10-minute default.

Copy Code, one-tap and zero-tap on Android and iPhone

The button decides how much work the user has to do. Here is what works where (PayPerWA offers Copy Code only):

OptionAndroidiPhoneWhat it needs
Copy CodeYesYesOnly the approved template; it is the fallback everywhere
One-tap autofillYesNo, Copy Code is shownYour app's package name and signing hash (up to 5 apps), a handshake from your app, and an activity for the com.whatsapp.otp.OTP_RETRIEVED intent
Zero-tapYesNo, Copy Code is shownEverything one-tap needs, plus accepting Meta's zero-tap terms; your app receives the code through a BroadcastReceiver
iOS keyboard suggestionsNot applicableiOS 26 and laterNo integration; on by default for authentication templates; numeric codes of 3 to 8 digits only

One-tap appears only when Meta's eligibility check passes: among other conditions, the handshake must be no older than 10 minutes (or the code expiry, if set), the package name and signing hash must match, and your app must declare the required activity. Otherwise WhatsApp shows Copy Code. Zero-tap falls back to one-tap, then to Copy Code. If you build either, note that Meta deprecated the older PendingIntent-based handshake on 15 October 2026; the WhatsApp OTP Android SDK is now the preferred method.

On iPhone, keyboard suggestions work only for numeric codes of 3 to 8 digits, only when WhatsApp is not in the foreground, and only if the user has WhatsApp notifications and Autofill Security Codes turned on. You can switch the feature off per template in WhatsApp Manager; there is no API setting for it. This is a good reason to use numeric codes of 6 to 8 digits: they fit the 15-character limit, qualify for iOS suggestions and meet NIST's minimum of six digits. Codes from the Verify API are numeric, 4 to 8 digits long and 6 by default.

On PayPerWA, the template builder creates Copy Code authentication templates and adds the button automatically. PayPerWA does not offer one-tap or zero-tap. Both need an Android app, so for web logins Copy Code (plus iOS keyboard suggestions for numeric codes) is what users get on any platform.

What a WhatsApp OTP costs in India

No subscription, no setup fee, no minimum monthly spend. You pay per OTP, and every OTP carries two separate charges, billed by two different parties:

ChargeBilled byIndia ratePaid from
Authentication feeMeta₹0.115 per delivered messageThe payment method on your WhatsApp Business account
Platform feePayPerWA₹0.20 per message sentYour prepaid PayPerWA wallet (refunded if the message fails)
Checking a code (Verify API)PayPerWAFreeNothing is charged
Per OTPBothMeta ₹0.115 + PayPerWA ₹0.20Two separate bills

₹0.115 (US$0.0014) is Meta's current list rate for authentication messages in India, read from Meta's official WhatsApp Business pricing calculator on 8 November 2026. Meta's pricing pages do not say how tax applies to it, so check your Meta invoices. Meta revises its rate cards from time to time, so recheck before you budget; the rate card by country lists the Meta rates PayPerWA displays.

At list rates, and assuming every message is delivered, 10,000 OTPs a month come to Meta ₹1,150 + PayPerWA ₹2,000, and 1,00,000 OTPs to Meta ₹11,500 + PayPerWA ₹20,000. There is nothing on top: no subscription, and checks with /api/v1/otp/verify are free, but a resend is a new OTP and is charged again. The wallet's minimum recharge is ₹750 (see pricing).

  • Volume tiers. Meta discounts utility and authentication messages, not marketing, at high volume. Volume is counted per market and category (for example, India authentication) across your whole business portfolio, not per number, and only charged messages count. Meta's calculator lists India authentication at ₹0.115 up to 7,50,000 charged messages a month and ₹0.1081 from there to 1.5 crore, stepping down to ₹0.0805 above 10 crore. Each rate applies only to the messages within its tier, so unless your portfolio sends more than 7,50,000 charged authentication messages to Indian users in a month, every OTP is billed at the list rate.
  • No free window for OTPs. Authentication messages are charged inside a 24-hour customer service window too, and the 1,000 free service messages per number per month do not cover them. The one free case that applies is a free entry point window, which opens when a user reaches you through a Click-to-WhatsApp ad and you reply within the customer service window.
  • Authentication-international rates. An Indian business sending codes to Indian numbers always pays the domestic rate. A business becomes eligible for authentication-international rates when it sends more than 7,50,000 messages outside customer service windows in a moving 30 days, across all its Messaging accounts, to users in Meta's 18 authentication-international markets, and eligibility is permanent. From a start date Meta sets 30 days later, it pays the higher rate when messaging users in those markets outside its own country. India is one of those markets: Meta's calculator lists India's authentication-international rate at ₹2.4971 (US$0.0304), which an eligible business based outside India pays for codes to Indian users. Likewise, an eligible Indian business pays, for example, the UAE's rate for users there.
  • Users outside India. Meta charges the authentication rate of the recipient's country, billed to your WhatsApp Business account; the rate card lists it by country. PayPerWA's fee stays ₹0.20 per message (US$0.004 on a US-dollar wallet).
  • INR billing deadline. Eligible India businesses must move every Messaging account in their portfolio to INR billing by 31 December 2026. From 1 January 2027, Meta will not deliver messages from non-INR accounts of eligible businesses, which for a login flow means codes that never arrive.

For context, on 8 November 2026 MSG91 listed OTPs at ₹0.25 (at 5,000) down to ₹0.18 (at 8,55,556), and Fast2SMS listed ₹0.25 down to ₹0.11 per SMS by recharge slab, both plus 18% GST, and SMS senders also need DLT registration. At low volume the two channels cost about the same per code; at high volume bulk SMS can cost less per message. WhatsApp OTP is not automatically cheaper, so compare your own quotes. The full breakdown is in WhatsApp OTP vs SMS OTP in India.

Setting up WhatsApp OTP on PayPerWA

There is no DLT registration and no subscription to set up. Four steps take you from sign-up to a verified code:

  1. Create an account and connect your number. Sign up and connect your WhatsApp Business number from the dashboard. Meta requires every business to send through its own Messaging account and number. Make sure your WhatsApp Business account has a working payment method, because Meta bills its ₹0.115 per delivered OTP there, and add balance to your PayPerWA wallet for the ₹0.20 fee (the minimum recharge is ₹750).
  2. Create the authentication template. Open Dashboard → Templates → New and choose the Authentication category. Meta reviews the template before you can send with it. The builder has three settings:
    • Add security recommendation is on by default. Keep it on.
    • Code expires in (minutes) accepts 1 to 90 and defaults to 15. Setting it to 10 lines it up with Meta's 10-minute default TTL for new authentication templates.
    • The Copy Code button is added for you. Meta generates the body, and there is no header.
    The Verify API sends with your most recently updated approved Authentication template, in that template's language, unless you name one with template_name, so you do not have to pass either. If Meta reports your business as not verified or still pending verification, PayPerWA stops the submission and shows a link to complete business verification. Until a template is approved, the Verify API answers 400 OTP_TEMPLATE_REQUIRED.
  3. Create an API key. In Dashboard → Settings → API, create a key. Keys start with ppw_live_sk_, and new keys include the messages:send permission, which both OTP calls use. Keep the key in an environment variable on your server, never in a mobile app or browser code where it can be extracted.
  4. Send a code, then check it. When the user asks for a code, your server calls /api/v1/otp/send with their number. When they type the code in, it calls /api/v1/otp/verify with the number and the code. PayPerWA never returns the code to you, so it cannot leak from your own API responses.
curl -X POST https://payperwa.com/api/v1/otp/send \
  -H "Authorization: Bearer $PAYPERWA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "919876543210"}'

curl -X POST https://payperwa.com/api/v1/otp/verify \
  -H "Authorization: Bearer $PAYPERWA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "919876543210", "code": "483920"}'

Optional but recommended: from Dashboard → Settings → API, open Webhook Events, register an HTTPS endpoint, subscribe to at least message.delivered and message.failed, and store the signing secret, which starts with whsec_. If you prefer to generate codes yourself, note the template's exact name and language code (the builder defaults to en), because /api/v1/messages/send needs both. To look up template names from code, GET https://payperwa.com/api/v1/templates?category=AUTHENTICATION returns your approved authentication templates with each one's name, category and language. It needs the templates:read permission. The WhatsApp OTP API page summarises both calls and the OTP API reference lists every field.

Sending an OTP through the PayPerWA API

With the Verify API, a successful POST https://payperwa.com/api/v1/otp/send returns a verification_id and the charge, never the code (abridged below). Besides to, which takes the country code and number, the optional fields are template_name and language (by default, your most recently updated approved Authentication template and its language), code_length (4 to 8 digits, default 6), expiry_minutes (1 to 30, default 10) and channelId if you run more than one number. The body is strict, so unknown fields return a 400. In to, a leading plus or 00, spaces and dashes are stripped, so +91 98765-43210, 919876543210 and 0091 98765 43210 are the same number with one set of limits. PayPerWA does not add 91 for you, and a number that still starts with 0 is refused.

{
  "success": true,
  "data": {
    "verification_id": "0b6f8f1e-6c1d-4f43-9a51-2f3c8f1d7a20",
    "to": "919876543210",
    "status": "pending",
    "expires_at": "2026-11-09T10:50:00.000Z",
    "code_length": 6,
    "template_name": "login_otp",
    "message_id": "wamid.HBgM...",
    "cost": {
      "meta_fee": 0,
      "platform_fee": 0.2,
      "wallet_deduction": 0.2,
      "total_outlay": 0.2,
      "billing_mode": "meta_direct",
      "country": "IN"
    }
  }
}

Because Meta bills its fee to your WhatsApp Business account, meta_fee is 0 and only PayPerWA's ₹0.20 leaves the wallet. Store message_id if you use webhooks; they refer to it. POST https://payperwa.com/api/v1/otp/verify takes the code plus either to or verification_id, costs nothing, and answers every well-formed request with HTTP 200, valid and one of five statuses:

StatusMeaningWhat to do
approvedvalid is true and the code is now used upSign the user in
incorrectWrong code, including an older code after a resend; attempts_remaining says how many tries are leftAsk the user to try again
max_attemptsThe 5th wrong code cancelled itOffer a new code
expiredPast expires_at; after this answer, or from 2 minutes past expiry, checks return not_foundOffer a new code
not_foundNo pending code for this number under your account: none was sent, it was used, cancelled or expired, or (when you check by verification_id) a resend replaced itOffer a new code

Errors return { "success": false, "error": "..." }, some with a code as well. The Verify API's send call shares its checks, charging and Meta error handling with /api/v1/messages/send, so the same statuses apply to both:

StatusMeaning
400Validation failed (with details), invalid channel, OWN_NUMBER (you sent to your own business number) or WhatsApp account not connected; on the Verify API also a body that is not JSON, OTP_TEMPLATE_REQUIRED (no approved Authentication template) or OTP_TEMPLATE_NOT_AUTHENTICATION (template_name names a template of another category)
401Missing, malformed, unknown, expired or deactivated API key
402INSUFFICIENT_WALLET_BALANCE, with the required and available amounts and the wallet's currency; nothing is sent
403Key lacks messages:send, or the account is suspended. CONTACT_OPTED_OUT applies only to Marketing and Utility templates, so it never blocks an OTP
404Template not found or not approved (on the Verify API, the one named in template_name)
409META_API_OFF: the connected access token can no longer reach your WhatsApp Business account; reconnect WhatsApp under Dashboard → Settings → WhatsApp Channels
429Per-key rate limit exceeded; on the Verify API also OTP_COOLDOWN (a code went to this number less than 30 seconds ago), OTP_TOO_MANY_SENDS (5 codes to this number in the past hour) or OTP_TOO_MANY_FAILED_CHECKS (15 wrong codes entered for this number in the past 24 hours), each with retry_after_seconds and a Retry-After header, and nothing sent or charged
503OTP_UNAVAILABLE: verification is briefly unavailable; nothing was sent or charged
Meta's statusWhatsApp API error, with Meta's error body in details
500An unexpected error, such as Failed to send message

If a successful response includes delivery_warning, Meta accepted the message but reports your WhatsApp Business account as blocked from sending (for example, because of a payment-method problem), so the code will most likely not be delivered.

  • Rate limit: each API key allows 100 requests a minute by default, and that 429 carries no Retry-After header. Verify calls count too, so one Verify API login takes at least two requests: a send and one or more checks. There is no dashboard setting to change it, so if your login peak is higher, contact support before launch.
  • Retries: there is no idempotency key. On /api/v1/otp/send, a repeat to the same number within 30 seconds is refused with OTP_COOLDOWN and costs nothing; a later one is a new, charged OTP that replaces the previous code. On /api/v1/messages/send, retrying a request that timed out can send, and charge, twice. Let the user tap Resend instead of retrying blindly.
  • Charge first, refund on failure: PayPerWA deducts its fee before calling Meta. If Meta rejects the send, the fee is refunded at once; if Meta accepts it and later reports it failed, the fee is refunded once and message.failed fires. PayPerWA can refund only its own fee. On the Verify API, a send rejected immediately (any error response) leaves nothing behind: the new code is discarded, the attempt does not count toward the limits, any fee taken is refunded, and an earlier code for that number stays valid. A send that Meta accepts and later reports as failed is refunded, but it still counts toward the 30-second cooldown and the hourly cap.

Sending your own code instead. If your server generates and verifies codes, send each one with POST https://payperwa.com/api/v1/messages/send. For an authentication template, put the code in variables[0]. PayPerWA fills both the message body and the Copy Code button from that one value, so you do not send a button parameter. Always pass the code: an authentication send without variables attaches no button parameter.

  • to: digits with the country code, no plus sign or spaces, for example 919876543210. PayPerWA passes it to Meta exactly as given.
  • language: the template's exact language code. It defaults to en and is not read from the stored template, so a template created as en_US or hi needs that value.
curl -X POST https://payperwa.com/api/v1/messages/send \
  -H "Authorization: Bearer $PAYPERWA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "919876543210",
    "template_name": "login_otp",
    "language": "en",
    "variables": ["483920"]
  }'

It returns the message_id, a status of sent and the same cost object. For both paths in more languages, see WhatsApp OTP login code examples; the OTP API reference and the API docs cover every field.

Tracking delivery and failures with webhooks

PayPerWA has no endpoint for looking up a message's status. Statuses reach you through webhooks, and you can also see them in the dashboard. Each event is a POST of { event, timestamp, data }. For API sends, including Verify API sends, data.sentFrom is "api" and data.wamid equals the message_id from the send response. Failed events include errorCode and errorMessage. Only message.sent carries the template name, so match later events by wamid. Read receipts depend on the recipient's WhatsApp settings, so never wait for a read.

The X-PayPerWA-Signature header is sha256= followed by a hex HMAC-SHA256 of the raw request body, keyed with your webhook secret. Compute it over the raw bytes, not over re-serialised JSON:

import express from "express";
import crypto from "node:crypto";

const app = express();

// Use the RAW body: PayPerWA signs the exact bytes it sends.
app.post("/webhooks/payperwa", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.PAYPERWA_WEBHOOK_SECRET) // whsec_...
    .update(raw, "utf8")
    .digest("hex");
  const got = req.get("X-PayPerWA-Signature") || "";
  if (got.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
    return res.sendStatus(401);
  }

  const { event, data } = JSON.parse(raw); // { event, timestamp, data }
  if (data.sentFrom === "api") {
    if (event === "message.delivered") {
      // data.wamid === message_id returned by /otp/send or /messages/send
    } else if (event === "message.failed") {
      // data.errorCode / data.errorMessage explain why: offer a resend or SMS.
      // PayPerWA has already refunded its own fee for this message.
    }
  }
  res.sendStatus(200); // answer within 8 seconds; deliveries are not retried
});

Each delivery is attempted once with an 8-second timeout and is not retried. Reply quickly, do slow work afterwards, and let users request a new code even if no webhook ever arrives.

Generating and verifying the code: Verify API or your own server

With the Verify API, PayPerWA does this part. Each code comes from a cryptographic random number generator and is stored only as a keyed hash. It expires (after 10 minutes unless you choose 1 to 30), works once, and is cancelled after 5 wrong attempts. A number can be sent a new code only after 30 seconds and at most 5 times an hour, and once 15 wrong codes have been entered for it in a rolling 24 hours, it gets no new code until older ones pass the 24-hour mark. These limits apply per number within your account and are checked before anything is charged. The 5-attempt count is per code, so a resend starts a fresh count, but the 24-hour count of wrong codes carries across resends. If you want NIST's stricter rule that a new code never resets the failed-attempt count, keep your own counter per number and purpose, and stop requesting codes once it is reached.

What stays yours on either path: a CAPTCHA or bot check on the form that triggers sends, limits per IP address and device, an allow-list of the countries you serve, and spend alerts. The Verify API's limits are per number, so they do not stop a bot sending one code each to many different numbers. OWASP's API Security Top 10 (API4:2023) lists paid per-request resources such as SMS among the things attackers abuse to run up costs and recommends spending limits, and a bot hitting your OTP form spends your balance whatever the channel.

If you send your own codes through /api/v1/messages/send, PayPerWA does not generate, store or verify them, so all of it is your server's job. The essentials, drawn from OWASP and NIST SP 800-63B-4:

  • Generate codes with a cryptographically secure random generator, at least six digits long. OWASP suggests eight or more where usability allows.
  • Store a keyed hash, never the plain code, and never write codes to logs.
  • Expire codes within 10 minutes and accept each one only once.
  • Limit wrong attempts (the sample below allows five, then locks the number for 15 minutes). On resend, replace the old code but keep the failed-attempt count: NIST says generating a new code must not reset it.
  • Rate-limit sends per phone number with a growing cooldown. On this path PayPerWA limits requests per API key, not per recipient.
import crypto from "node:crypto";

const OTP_TTL_MS = 10 * 60 * 1000;         // match the template's "expires in 10 minutes"
const MAX_ATTEMPTS = 5;                     // wrong codes allowed before a lock-out
const LOCK_MS = 15 * 60 * 1000;             // lock-out length; lengthen it on repeat lock-outs
const OTP_PEPPER = process.env.OTP_PEPPER;  // long random server-side secret
const store = new Map();                    // use Redis or your database in production

function hashCode(phone, code) {
  return crypto.createHmac("sha256", OTP_PEPPER).update(`${phone}:${code}`).digest();
}

export function createOtp(phone) {
  const prev = store.get(phone);
  if (prev && prev.lockedUntil > Date.now()) return null; // locked: don't send a new code yet
  const code = crypto.randomInt(0, 1_000_000).toString().padStart(6, "0");
  store.set(phone, {
    hash: hashCode(phone, code),
    expiresAt: Date.now() + OTP_TTL_MS,
    // a resend must not reset failed attempts; only a finished lock-out does
    attempts: prev && !prev.lockedUntil ? prev.attempts : 0,
    lockedUntil: 0,
  });
  return code; // send it with PayPerWA; never log it
}

export function verifyOtp(phone, submitted) {
  const entry = store.get(phone);
  if (!entry) return { ok: false, reason: "no_code" };
  if (entry.lockedUntil > Date.now()) return { ok: false, reason: "locked" };
  if (Date.now() > entry.expiresAt) return { ok: false, reason: "expired" };

  entry.attempts += 1;
  const candidate = hashCode(phone, String(submitted).trim());
  if (crypto.timingSafeEqual(entry.hash, candidate)) {
    store.delete(phone); // single use
    return { ok: true };
  }
  if (entry.attempts >= MAX_ATTEMPTS) {
    entry.lockedUntil = Date.now() + LOCK_MS;
    entry.expiresAt = 0; // the current code is dead too
    return { ok: false, reason: "locked" };
  }
  return { ok: false, reason: "wrong_code" };
}

For more detail, see our OTP security best practices guide.

When SMS fallback still matters

PayPerWA sends WhatsApp only. It has no SMS, voice or email fallback, so if you need one, keep your SMS provider and trigger it from your own code. You will want it when:

  • The user has no WhatsApp on that number. Let them choose on screen ("Send on WhatsApp" or "Send by SMS"), and on Android show the WhatsApp option only when WhatsApp is installed.
  • The send returns a Meta error, or a message.failed webhook arrives.
  • No message.delivered event arrives after a wait you choose, such as 30 to 60 seconds. Show a "Send by SMS instead" link rather than silently sending both.
  • The send response contains delivery_warning, meaning Meta reports your WhatsApp account as blocked from sending.
  • The user mostly uses WhatsApp Web or desktop. Authentication messages show only on the primary phone, so tell users on the code screen to check WhatsApp on their phone.

One code store or two. The Verify API checks only codes it sent. If a user falls back to SMS, that code comes from your SMS provider or your own server and is checked there. There is no call to cancel a Verify code, so the WhatsApp code stays valid until it expires, is used or runs out of attempts; decide which store to check, for example your SMS store first, then /api/v1/otp/verify. If you would rather keep one code store across both channels, generate codes yourself and send the WhatsApp leg with /api/v1/messages/send.

If you handle digital payments, note that RBI's 2025 authentication directions require at least two-factor authentication but do not call for discontinuing SMS-based OTP as an authentication factor, so keeping SMS available is consistent with them.

Keep the fallback alive. Under TRAI's 2025 amendment, operators temporarily deactivate DLT headers and content templates that have not been used for 90 days, until the sender asks for them to be reactivated. A fallback that rarely fires can quietly stop working, so test it on a schedule and keep your entity, header, template and message-path registrations current.

Be careful with account recovery. Phone numbers get recycled, and the WhatsApp account on a number can still belong to its previous owner. Meta advises against treating a phone number and its WhatsApp account as the same thing, so do not let a single OTP unlock a sensitive change, such as a new email address, without another check.

Decision checklist before you switch

  1. Most of your users have WhatsApp on the number they sign up with, or you offer a choice of channel.
  2. Your opt-in wording names your business and makes clear users will hear from you.
  3. Your WhatsApp Business account has a working payment method for Meta's fee, and you have checked whether the INR billing deadline applies to you.
  4. You have an approved Authentication template. The Verify API picks it for you; for /api/v1/messages/send you also know its exact name and language code.
  5. The expiry footer, Meta's TTL and your server-side expiry agree, ideally at 10 minutes each.
  6. Codes are generated, hashed, expired and verified with attempt limits and resend cooldowns, by the Verify API or by your server, and your request form has bot protection, IP limits and a country allow-list.
  7. You budget each OTP as Meta ₹0.115 + PayPerWA ₹0.20 and have compared that with your SMS quote.
  8. Your webhook endpoint is HTTPS, checks the signature and answers within 8 seconds.
  9. Your peak stays within 100 API requests a minute per key, counting verify calls (at least two requests per Verify API login), or you have spoken to support.
  10. An SMS fallback exists and was tested recently.

If most of these are true, you are ready to offer WhatsApp OTP. To try the flow, create a PayPerWA account, connect your number, submit an Authentication template and send a test code to a phone other than your business number. There is no subscription and no DLT registration: Meta bills its fee to your WhatsApp Business account and PayPerWA charges ₹0.20 per OTP from your wallet. The WhatsApp OTP API page sums up the Verify API, and the OTP API reference has every field. See pricing, the features page and the help centre, or read the rest of this series on the blog.

Frequently Asked Questions

Does PayPerWA generate or verify the OTP?
Yes, if you use the Verify API: POST /api/v1/otp/send generates, sends and expires the code, and POST /api/v1/otp/verify checks what the user typed. PayPerWA never returns the code to you. If you prefer to generate and verify codes on your own server, send them with /api/v1/messages/send in variables[0].
How much does a WhatsApp OTP cost in India?
Two separate charges per OTP: Meta ₹0.115 at Meta's current India list rate, billed to the payment method on your WhatsApp Business account, plus PayPerWA ₹0.20 from your prepaid wallet. Verifying a code is free, and there is no subscription, setup fee or minimum monthly spend. Authentication messages are charged even inside the 24-hour customer service window.
Can I add my brand name, a link or an emoji to the OTP message?
No. The body is fixed text set by Meta, and URLs, media and emojis are not allowed. You can only switch on the security line and the expiry footer, and the code can be at most 15 characters.
Does the user have to message my business before I can send an OTP?
No. Template messages, including authentication templates, can be delivered at any time. You still need the user's opt-in to receive messages from your business.
Do I need DLT registration to send WhatsApp OTPs in India?
No. DLT entity, header and template registration applies to SMS. A WhatsApp OTP needs an approved WhatsApp Authentication template instead, which you create in Dashboard → Templates → New → Authentication and Meta reviews. If you also send SMS as a fallback, that part still needs DLT.
Does OTP autofill work on iPhone?
One-tap and zero-tap are Android-only, so iPhone users see Copy Code. On iOS 26 and later the keyboard can suggest numeric codes of 3 to 8 digits from the WhatsApp notification, provided WhatsApp is not open in the foreground.
Will PayPerWA fall back to SMS if WhatsApp fails?
No. PayPerWA sends WhatsApp only. Keep your own SMS provider and trigger it from your code when you receive a message.failed webhook or see no delivery after a short wait.

Create your account and send your first campaign today

Free account. No subscription — just ₹0.20 per message + Meta's rate (India marketing ₹0.86, utility ₹0.115).

Share this article

P

PayPerWA Team

We build PayPerWA, a pay-per-message WhatsApp Cloud API platform. No subscriptions, no hidden fees — Meta's rate plus a flat ₹0.20 per message.

Try PayPerWA — Just 20 Paisa Per Message

No subscription. No monthly fee. Just ₹0.20 platform fee + Meta's standard API charges.

Create free account

Related Articles

Guide

How to Apply for WhatsApp Business API in 2026 (Free, Step-by-Step)

A complete 2026 walkthrough of how to apply for and get the WhatsApp Business API — prerequisites, Meta Embedded Signup, choosing a number, display name, business verification, and going live. It is free to obtain; PayPerWA charges no BSP onboarding fee.

28 May 202613 min read
Guide

How to Migrate from WhatsApp Business App to the API (2026)

A complete 2026 guide to moving from the free WhatsApp Business App to the WhatsApp Business API — when to migrate, how Coexistence can let you keep the app if Meta finds your number eligible, the step-by-step process, what happens to your number and chat history, and what it actually costs.

22 May 202612 min read
Chat with us