WhatsApp OTP API: How to Send Login Codes Properly
Your signup flow sends a six-digit code by SMS. The bill keeps growing, some carriers deliver late, and users in markets where everyone lives in WhatsApp still wait for a text. Moving codes to WhatsApp looks like an easy win, and it often is, as long as you use the right part of the platform.
A WhatsApp OTP API is not "send a message with six digits in it". Meta has a dedicated template category for one-time passwords, with fixed wording, special buttons, its own pricing and its own expiry rules. Get those right and codes arrive fast and cheaply. Get them wrong and your template is rejected, your bill doubles, or your number gets flagged.
This guide covers how authentication templates work, the three delivery styles, what they cost as of October 2026, how to build an SMS fallback, and why a paced, linked-number API (ours included) is the wrong tool for codes.
Key takeaways
- OTPs on the official platform go out as authentication templates. Meta fixes the wording; you supply the code.
- Pick a button style: copy code works everywhere, one-tap and zero-tap need an Android app handshake.
- Authentication messages are billed per delivered message, by the recipient's country, with volume tiers.
- Set a short time-to-live and fall back to SMS when no delivery receipt arrives in time.
- Do not send codes through a paced linked-number API. It is too slow and looks like spam.
Use authentication templates, not ordinary messages
On Meta's WhatsApp Business Platform, a code you send to someone who has not just messaged you must go out as an approved template, and codes belong in the authentication category. You do not write the body. Meta's preset text reads <code> is your verification code., with an optional security line ("For your security, do not share this code.") and an optional footer saying the code expires in a set number of minutes (1 to 90).
The restrictions are strict. Per Meta's copy code template docs, the code can be at most 15 characters, and URLs, media and emojis are not supported. That is deliberate: a login code with a link in it is a phishing pattern.
Do not try to dodge the category by sending codes on a utility or marketing template. Meta's template categorization rules say a template that should have been authentication gets notice and is then set to REJECTED on the first day of the following month, with no review available. Our guide to WhatsApp template categories covers the other lines Meta draws.
Why a paced linked-number API is the wrong tool
Some APIs, Tenashi's included, send through an ordinary WhatsApp number linked as a device, like WhatsApp Web. That model is built for conversations, so sends are deliberately slow. Tenashi sends one message every 4 to 12 seconds per number and allows 50 new chats per number per day through the API.
An OTP needs the opposite: delivery in seconds, to people who have never written to you, sometimes thousands an hour. A linked number has no copy-code button and no autofill, and a personal or Business app number firing codes at strangers is exactly the pattern WhatsApp bans. Use the official authentication templates, directly on Cloud API or through a provider.
Copy code, one-tap or zero-tap
Authentication templates come with one of three button styles. The template body is the same; the difference is how the code gets from WhatsApp into your app.
| Style | What the user does | Where it works | What you build |
|---|---|---|---|
| Copy code | Taps a button, pastes the code | Every WhatsApp client | Nothing beyond the template |
| One-tap autofill | Taps a button that opens your Android app with the code | Android app; Meta says iOS 26 and later offers keyboard autofill with no integration | App handshake, package name and signing hash |
| Zero-tap | Nothing. Your Android app receives the code in the background | Android only | Broadcast receiver, handshake, package name and signing hash |
Most teams start with copy code because it covers web signups, iOS and desktop. Add one-tap or zero-tap later if most of your logins happen inside your own Android app. Meta's authentication templates overview has the handshake steps.
Create and send an authentication template
You create the template once against your WhatsApp Business Account, then send it with a fresh code each time. The examples use v23.0; use the current Graph API version from Meta's changelog.
curl https://graph.facebook.com/v23.0/WABA_ID/message_templates \
-H "Authorization: Bearer $WHATSAPP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "login_code",
"language": "en_US",
"category": "authentication",
"message_send_ttl_seconds": 300,
"components": [
{ "type": "body", "add_security_recommendation": true },
{ "type": "footer", "code_expiration_minutes": 5 },
{ "type": "buttons", "buttons": [
{ "type": "otp", "otp_type": "copy_code", "text": "Copy code" }
] }
]
}'
Sending puts the code in two places: the body parameter and the button parameter. Meta converts the OTP button to a URL button on creation, which is why the send uses sub_type: url.
curl https://graph.facebook.com/v23.0/PHONE_NUMBER_ID/messages \
-H "Authorization: Bearer $WHATSAPP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"messaging_product": "whatsapp",
"to": "919811122233",
"type": "template",
"template": {
"name": "login_code",
"language": { "code": "en_US" },
"components": [
{ "type": "body", "parameters": [ { "type": "text", "text": "482913" } ] },
{ "type": "button", "sub_type": "url", "index": "0",
"parameters": [ { "type": "text", "text": "482913" } ] }
]
}
}'
Store the returned message id with the code request. You will need it to match the delivery webhook in the fallback logic below.
What WhatsApp OTPs cost
Authentication is its own pricing category. As of October 2026, per Meta's pricing page, you pay per delivered message, and the rate depends on the recipient's country calling code. Meta publishes the rates as rate cards and can only change them on the first day of a quarter.
Three details matter for OTP budgets:
- Volume tiers apply. Authentication and utility are the only categories with volume discounts. Tiers are per market, aggregated across your business portfolio, and reset monthly. See how WhatsApp API volume tiers work.
- Authentication-international rates. From October 1, 2026, a business based in one country sending authentication templates to users in certain other markets may be charged a separate international rate, in up to 18 markets.
- You pay on delivery. A code that is dropped because its time-to-live ran out is not delivered, so it is not billed.
For the line-by-line breakdown, read our explainer on authentication message pricing. Do not copy rates from blog posts, including ours. Check the rate card for your users' markets.
Set an expiry and build an SMS fallback
Every template has a time-to-live: how long WhatsApp keeps trying to deliver it. Per Meta's time-to-live docs, authentication templates default to 10 minutes (templates created before October 23, 2024 default to 30 days) and accept a custom value from 30 to 900 seconds. Set it to match your code's validity. A code that lands after it expired is worse than no code.
WhatsApp will not fall back to SMS for you. Build it:
- Create the code, hash it, store it with an expiry and the user's session.
- Send the authentication template and save the message id.
- Wait for a
deliveredstatus webhook for that id. Our post on message status webhooks shows the payload. - If the send fails, or no
deliveredarrives within a window you choose (many teams pick 15 to 30 seconds), send the same code by SMS. - Always show a "Send by SMS instead" link on the code screen, so the user can choose without waiting.
def deliver_code(user, code):
wa_id = send_whatsapp_auth(user.phone, code) # template send above
save_attempt(user, channel="whatsapp", message_id=wa_id)
schedule(fallback_check, user_id=user.id, message_id=wa_id, after_seconds=20)
def fallback_check(user_id, message_id):
if not status_seen(message_id, "delivered"): # filled by your webhook
send_sms(get_user(user_id).phone, current_code(user_id))
Keep one code valid across both channels. Issuing a second code for SMS confuses users who then receive the first one on WhatsApp.
A security checklist for WhatsApp codes
Moving to WhatsApp does not remove OTP abuse. International authentication rates make pumping fraud, where attackers trigger codes to expensive destinations, a real cost.
- Rate-limit code requests per phone number, per IP and per account.
- Block or challenge requests to countries you do not serve.
- Store a hash of the code, never the plain value, and expire it on your server regardless of the template TTL.
- Bind the code to the session that requested it and cap verification attempts.
- Keep codes out of logs and analytics events.
- Create the template in each language you support and pick it from the user's locale.
The WhatsApp API vs SMS comparison covers when SMS should stay your primary channel.
Where Tenashi fits
Tenashi is not an OTP service, and we would rather say so than sell you the wrong tool. It links your existing WhatsApp or WhatsApp Business number as a device and paces every send for ban protection. It has no authentication templates, no copy-code button and no autofill. Send codes with Meta's authentication templates, directly on Cloud API or through a provider. The complete guide to the WhatsApp API compares the routes.
Where Tenashi helps is everything around the login: the user who messages your support number because a code never arrived, onboarding questions, sales follow-ups and customer groups. Your team works those chats in one shared inbox with a CRM under every conversation, on the number customers already know, from $18/mo.
See every feature · Get started
FAQ
Can I send OTPs from a WhatsApp Business app number?
Not reliably or safely. The Business app has no authentication templates, no copy-code button and no delivery guarantees for high volume, and messaging strangers with codes from an app number invites a ban. Use Meta's authentication templates on Cloud API, either directly or through a provider, and keep the app number for conversations.
What is the difference between one-tap and zero-tap?
With one-tap, the user taps a button in the WhatsApp message and your Android app opens with the code filled in. With zero-tap, the user does nothing: your Android app receives the code in the background. Both need an app handshake, package name and signing hash. Copy code works everywhere without extra setup.
How long should a WhatsApp OTP stay valid?
Match the template time-to-live to your code's validity, usually a few minutes. Authentication templates accept a custom TTL from 30 to 900 seconds, with a 10-minute default for newer templates. Expire the code on your own server too, since the TTL only controls delivery attempts, not whether your backend still accepts the code.
Do I pay for OTPs that are never delivered?
No. Meta charges per delivered message, so a code that fails or is dropped when its time-to-live expires is not billed. You still pay for codes that are delivered but never used, which is why rate limiting and blocking unexpected countries matter for keeping authentication spend under control.
Should I keep SMS for OTP after moving to WhatsApp?
Yes, as a fallback. Some users do not have WhatsApp, some have it on another number, and some are offline. Send WhatsApp first, wait briefly for a delivery receipt, then send the same code by SMS. A visible "send by SMS instead" option also keeps impatient users from abandoning signup.
Codes belong on authentication templates; conversations belong with your team. When the chats around signup start piling up on one phone, see how Tenashi handles them.