WhatsApp Authentication Templates: Copy Code, One-Tap and Zero-Tap Explained
WhatsApp OTPs must go out as authentication templates, with Meta's fixed text and one of three button types. This guide covers each option, its platform limits, approval pitfalls, the exact send payload and how PayPerWA's Verify API picks your template.
Key Takeaways
- WhatsApp OTPs must be sent with an authentication template, whose body text is fixed by Meta and cannot include your own copy, links, media or emojis.
- You control only an optional security line, an optional expiry footer of 1 to 90 minutes, the button type and label, and the message time-to-live.
- Copy code works everywhere and is the fallback, while one-tap autofill and zero-tap work only in native Android apps that pass Meta's handshake and eligibility checks, and PayPerWA does not offer them.
- A 6-digit numeric code fits Meta's 15-character limit and works with keyboard suggestions on iOS 26 and later, which ignore alphanumeric codes.
- PayPerWA's builder creates copy code authentication templates, and its Verify API sends with your approved one automatically or the one named in template_name, at Meta ₹0.115 + PayPerWA ₹0.20 per OTP in India with no subscription.
What an authentication template is and when you must use one
If your app or website lets people receive a one-time password (OTP) or verification code on WhatsApp, Meta requires you to send it with an authentication template. Meta's categorization guidelines say that only authentication templates can carry a one-time passcode for identity verification. Marketing and utility templates cannot be used for that purpose.
Authentication templates work differently from the templates you use for offers or order updates. You do not write the message. Meta supplies the wording, you switch a few options on or off around it, and you attach a one-time password button. This guide walks through each part, the three button types and their platform limits, approval, languages, what PayPerWA's builder supports, and the payload that delivers the code.
Three rules apply before you build anything:
- You do not need to wait for the user to message you. Template messages can be delivered at any time. Only free-form messages are limited to the 24-hour customer service window.
- You still need opt-in. Meta requires opt-in before messaging people on WhatsApp, with no exemption for OTPs. Since the November 2024 policy update, opt-in can be general rather than WhatsApp-specific, provided it complies with local law, makes clear the person is opting in to communication from your business, and names your business.
- No DLT registration. India's DLT entity, header and content-template registration applies to SMS. For a WhatsApp OTP, the approved authentication template is the gate, and on PayPerWA you then pay per OTP sent, with no subscription.
If you are still deciding whether WhatsApp should carry your codes at all, start with our overview of the WhatsApp OTP API in India and the side-by-side WhatsApp OTP vs SMS OTP comparison.
The fixed body text, security recommendation and expiry footer
Every authentication template has the same structure: a body, an optional footer and a one-time password button. The body is fixed. You control two optional lines, the button, and how long WhatsApp keeps trying to deliver the message.
| Part | Who writes it | Your options |
|---|---|---|
| Body | Meta (preset, localized by Meta) | None. In English: {{1}} is your verification code. |
| Security recommendation | Meta | On or off (add_security_recommendation) |
| Expiry footer | Meta | Off, or 1 to 90 minutes (code_expiration_minutes) |
| OTP button | Meta's preset label, or your own | Copy code, one-tap or zero-tap; optional label of up to 25 characters |
| Time-to-live | Not shown to the user | 30 to 900 seconds, or -1 for 30 days; default 10 minutes |
| The code itself | PayPerWA's Verify API, or your server at send time | Up to 15 characters; no URLs or emojis |
The body. Meta describes it as fixed, non-customizable preset text. In English it reads {{1}} is your verification code., with the code in bold. You cannot add your brand name, a link, an emoji or any other copy. URLs, media and emojis are not allowed in the content or the parameters either.
The security recommendation. Setting add_security_recommendation: true on the body adds the line "For your security, do not share this code." It is worth turning on for logins, payments and account recovery.
The expiry footer. Setting code_expiration_minutes on the footer adds "This code expires in N minutes." The value must be between 1 and 90; leave it out and no expiry warning is shown. Treat the number as a promise: your server should accept the code for the same period and reject it afterwards.
Time-to-live (TTL). TTL is how long WhatsApp keeps trying to deliver the message before dropping it. For authentication templates created on or after 23 October 2024 the default is 10 minutes (older templates default to 30 days). Meta recommends a TTL equal to or less than your code expiration time, so a phone that comes back online late does not receive an expired code.
Protections you get automatically. On the Cloud API, authentication messages are delivered only to the user's primary WhatsApp device and are masked on linked devices such as WhatsApp Web. This cannot be switched off. WhatsApp also disables forwarding of authentication messages to make phishing harder.
No subscription. ₹0.20/msg + Meta's rate (or $0.004/msg on USD wallets).
Create free account →Copy code, one-tap and zero-tap compared
The button is where authentication templates differ most. In Meta's template API the button type is OTP, and otp_type is one of copy_code, one_tap or zero_tap. The button label is optional, can be up to 25 characters, and defaults to a localized preset: "Copy Code" for copy code and "Autofill" for one-tap in US English.
| Button type | What the user does | Platforms | What your app must do | If it cannot be used | On PayPerWA |
|---|---|---|---|---|---|
Copy code (copy_code) | Taps the button; WhatsApp copies the code and the user pastes it into your app or site | All platforms | Nothing | It is the fallback for the other two | Yes, added automatically |
One-tap autofill (one_tap) | Taps "Autofill" and the code is passed to your Android app | Android only | Register package name and signing-key hash, perform a handshake, declare an activity for com.whatsapp.otp.OTP_RETRIEVED | A copy code button is shown instead | Not offered |
Zero-tap (zero_tap) | Nothing; WhatsApp broadcasts the code and your app captures it | Android only | The same supported_apps registration as one-tap, a BroadcastReceiver to capture the code, and zero_tap_terms_accepted: true | Falls back to one-tap, then copy code | Not offered |
Two consequences follow. Every template can end up as copy code, so copy code is the experience you must design for: iPhone users always get it, and so does any Android user whose app integration fails a check. And one-tap and zero-tap only help if you have a native Android app; a website login gets copy code.
One detail matters when you send: on creation, WhatsApp changes the button type from OTP to URL. That is why Meta's send payload uses sub_type: "url", even for copy code.
Copy code, and what iPhone users see
With copy code, the user taps the button, the WhatsApp client copies the code to the clipboard, and the user pastes it into your app or site. It needs no app work and is the fallback on every platform. You can rename the button, but if you leave the label out, Meta uses a default that is already localized for each template language.
Meta is also adding an "I didn't request a code" button. It is in beta and rolling out incrementally, so do not plan around it yet. When a user taps it, Meta sends a button webhook with the payload DID_NOT_REQUEST_CODE.
iPhone. One-tap and zero-tap do not work on iOS; iPhone users see a copy code button. On iOS 26 and later, Keyboard suggestions are on by default for all authentication templates: iOS reads the code from the WhatsApp notification and offers it in the keyboard, with no integration on your side. The limits:
- Only numeric codes of 3 to 8 digits are detected. Alphanumeric codes are not autofilled.
- It does not trigger when WhatsApp is in the foreground.
- The user needs notifications enabled for WhatsApp and Autofill Security Codes turned on.
- Earlier iOS versions have no autofill, so copy code is all those users get.
- You can opt a template out with the "Allow autofill on iOS" toggle. It exists only in WhatsApp Manager; there is no API parameter for it.
This is the main reason to prefer a 6-digit numeric code. It fits Meta's 15-character limit, iOS keyboard suggestions can pick it up, and it meets the six-digit minimum NIST SP 800-63B-4 sets for out-of-band codes. Letters are allowed, but they lose the iOS autofill. Our OTP security best practices guide covers code length, expiry and attempt limits.
One-tap autofill: Android requirements
One-tap autofill replaces the copy button with an "Autofill" button. When the user taps it, WhatsApp hands the code to your Android app, so there is nothing to paste. It is supported only on Android; on other devices WhatsApp shows a copy code button instead.
To create a one-tap template, you list up to 5 apps in supported_apps, each with a package_name (your Android package name) and a signature_hash (your app signing key hash, exactly 11 characters from a-zA-Z0-9+/=). Your app then has two jobs:
- Perform a handshake before you send the code. Meta defines it as an Android intent and a public class that you implement and the WhatsApp client can start.
- Declare an activity with the intent action
com.whatsapp.otp.OTP_RETRIEVED.
Before showing the Autofill button, WhatsApp runs an eligibility check. All of these must hold, or the user gets copy code:
- The handshake is no older than 10 minutes, or no older than
code_expiration_minutesif you set an expiry. - The package name and signing hash in the template match the app on the phone.
- The message includes autofill text.
- The receiving activity is defined.
The Autofill button is disabled after code_expiration_minutes, or after 10 minutes if no expiry is set.
Handshake migration. Meta extended its migration deadline to 15 October 2026 and said the PendingIntent-based handshake for authentication templates would be deprecated on that date, which has now passed. Its preferred path is the OTP Android SDK (com.whatsapp.otp:whatsapp-otp-android-sdk). If your app still uses the older handshake, move it to the SDK.
Meta also recommends showing the WhatsApp option on Android only when WhatsApp is installed, confirming the user's WhatsApp number before sending, and making clear that the code is going to WhatsApp.
Zero-tap: codes that arrive without a tap
Zero-tap goes one step further. The WhatsApp client broadcasts the code and your app captures it with a BroadcastReceiver, so the user never leaves your app. Like one-tap, zero-tap is supported only on Android. If zero-tap cannot be used, the message falls back to a one-tap button, then to copy code.
A zero-tap template needs the same supported_apps entry as one-tap, plus zero_tap_terms_accepted: true. If that flag is false, the template is not created. The button component looks like this (the package name and hash are examples):
{
"type": "buttons",
"buttons": [
{
"type": "otp",
"otp_type": "zero_tap",
"text": "Copy Code",
"autofill_text": "Autofill",
"zero_tap_terms_accepted": true,
"supported_apps": [
{
"package_name": "com.example.yourapp",
"signature_hash": "K8a/AINcGX7"
}
]
}
]
}
For a one-tap template, set otp_type to one_tap and leave out zero_tap_terms_accepted. text is the label of the copy code fallback and autofill_text the label of the Autofill button; both are optional.
Because the code arrives with no action from the user, Meta's zero-tap best practices ask for extra care:
- Do not make WhatsApp your default password or code delivery method. Offer it as a choice.
- Tell users that the code will be delivered to your app automatically.
- Confirm receipt once the code has arrived, so the user knows what happened.
Multi-language authentication templates
Because Meta writes the body, you never translate an authentication template yourself. You choose a language when you create it, and Meta supplies its localized preset text. The Spanish (es_ES) version, for example, reads Tu código de verificación es {{1}}., with the code at the end of the sentence rather than the start.
To serve users in more than one language, create one authentication template per language and note each one's name and exact language code. Three points catch people out:
- The language code must match exactly at send time.
enanden_USare different codes. With PayPerWA's/api/v1/messages/send, thelanguagefield defaults toenand is passed to Meta exactly as you send it; it is not read from the stored template, so a Hindi (hi) oren_UStemplate needs that code. The Verify API instead uses the chosen template's stored language unless you passlanguage. - Button labels need a check. Meta's localized default label applies only when you leave the label out. PayPerWA's builder submits the label "Copy Code" for every authentication template it creates.
- The code is the same in every language. Keep it numeric so iOS keyboard suggestions work everywhere.
Pick the template from the language each user chose in your app, not from their country code, which does not tell you which language a person reads. With the Verify API, pass that template's name as template_name, and pass language too if several of your templates share that name: template_name alone picks one copy and sends in that copy's language. Without template_name, Verify uses your most recently updated approved Authentication template, whatever its language. To list your approved authentication templates with their language codes, use a key with the templates:read permission (new keys include it):
curl "https://payperwa.com/api/v1/templates?category=AUTHENTICATION" \
-H "Authorization: Bearer YOUR_API_KEY"
Each template in the response includes its name, category and language.
Approval tips and common rejection reasons
Authentication templates have almost no free text, so most problems come from the wrong category, a setting outside Meta's limits, or a mismatch between template and send request.
- Use the Authentication category with an OTP button. To be classified as authentication, a template must use Meta's Cloud API authentication templates, have a copy code or one-tap button, and follow the content restrictions.
- Never send codes in a utility or marketing template. Meta does not recategorize them. Since 1 October 2024, after advance notice, they are set to REJECTED on the first day of the following month.
- Stay within the limits covered above, and do not try to customize the content.
- Use your own account and number. Sharing a Messaging account across businesses is against Meta's policy.
When something goes wrong, it is usually one of these:
| Symptom | Likely cause | Fix |
|---|---|---|
| An OTP template in the Utility or Marketing category is set to REJECTED | One-time passcodes are allowed only in authentication templates | Create an Authentication template and move your code to it |
| A zero-tap template is not created | zero_tap_terms_accepted is false | Set it to true |
| Android users see Copy Code instead of Autofill | No handshake, a handshake older than the allowed window, a package name or signing hash mismatch, no autofill text, or no OTP_RETRIEVED activity | Check each eligibility condition and migrate to the OTP Android SDK |
| iPhone users get no keyboard suggestion | Alphanumeric code, iOS earlier than 26, WhatsApp open in the foreground, notifications or Autofill Security Codes off, or the iOS autofill toggle switched off in WhatsApp Manager | Use a numeric code of 3 to 8 digits; the device settings are up to the user |
| The send is rejected by Meta | A code longer than 15 characters, or a language code that does not match the template | Shorten the code and pass the exact language code |
| Error 131062 | The message was sent to a BSUID using recipient | Send authentication templates to a phone number |
| PayPerWA returns 404, template not found or not approved | The template is still pending, was rejected or deleted, or the name is wrong | Wait for APPROVED status and check the exact name |
PayPerWA's Verify API returns 400 OTP_TEMPLATE_REQUIRED | No approved Authentication template on your account, or on the channel you chose | Create one in Dashboard → Templates → New → Authentication and wait for Meta's approval |
PayPerWA's Verify API returns 400 OTP_TEMPLATE_NOT_AUTHENTICATION | template_name names a Utility or Marketing template | Name an approved Authentication template, or leave template_name out |
PayPerWA blocks submission with BUSINESS_NOT_VERIFIED | Meta reports your business as not verified, or verification as pending | Complete business verification using the link shown, then submit again |
Creating an authentication template in PayPerWA
PayPerWA's template builder creates copy code authentication templates, which work on every platform. It is also the only registration a WhatsApp OTP needs: there is no DLT step. The flow:
- Open Dashboard → Templates → New.
- Enter a template name, for example
login_otp, and pick the language. The list contains Meta's official template languages and defaults to English (en). - Set the category to Authentication. The header card is hidden and the builder shows Meta's auto-generated body:
{{1}} is your verification code. - Leave Add security recommendation ticked. It is on by default.
- Set Code expires in (minutes) to a value from 1 to 90. The default is 15. Use the same expiry you enforce: the Verify API's
expiry_minutestakes 1 to 30 and defaults to 10, so set 10 if you keep that default. - The Copy Code button is added automatically, so there is nothing to configure.
- Click Submit for Approval. Meta reviews the template; once its status shows APPROVED, PayPerWA's Verify API can send with it straight away.
Behind the scenes, PayPerWA submits a body with your security setting, a footer with your expiry and a single copy code OTP button. If Meta reports your business as not verified or pending, submission is blocked with BUSINESS_NOT_VERIFIED and a link to complete verification.
What the builder does not do:
- No one-tap or zero-tap. PayPerWA does not offer these Android-only button types. Copy code works on every platform, and on iOS 26 and later numeric codes of 3 to 8 digits can also be suggested from the WhatsApp notification.
- No TTL setting. PayPerWA does not set a time-to-live, so Meta's default of 10 minutes applies. That stays within the builder's default 15-minute expiry, as Meta recommends. If you set an expiry shorter than 10 minutes, the default TTL is longer than the code's lifetime.
Sending the code: Meta's payload and the PayPerWA API
PayPerWA gives you two ways to send. With the Verify API, PayPerWA generates the code, sends it with your authentication template, expires it and checks it, so your server never handles the code. With /api/v1/messages/send, your server generates and checks the code and PayPerWA delivers it. Both cost the same per OTP. It helps to see the raw Meta payload first, because it shows what PayPerWA fills in for you.
The Cloud API payload. On Meta's Cloud API you send a template message whose components carry the code twice, once for the body and once for the button:
POST https://graph.facebook.com/API_VERSION/PHONE_NUMBER_ID/messages
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "919876543210",
"type": "template",
"template": {
"name": "login_otp",
"language": { "code": "en" },
"components": [
{
"type": "body",
"parameters": [ { "type": "text", "text": "483920" } ]
},
{
"type": "button",
"sub_type": "url",
"index": "0",
"parameters": [ { "type": "text", "text": "483920" } ]
}
]
}
}
The code can be up to 15 characters and must appear in both places. Authentication templates must be sent to a phone number; sending to a BSUID with recipient returns error 131062.
The PayPerWA Verify API. Send only the phone number to POST https://payperwa.com/api/v1/otp/send. PayPerWA picks your most recently updated approved Authentication template and its language, or the template you name in template_name (a template of another category is refused with OTP_TEMPLATE_NOT_AUTHENTICATION), and fills the body and the copy code button with a fresh numeric code: 6 digits by default, or 4 to 8 with code_length.
curl -X POST https://payperwa.com/api/v1/otp/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "919876543210",
"template_name": "login_otp"
}'
The response carries a verification_id, expires_at, the template_name used, the message_id and the cost, but never the code. When the user types the code, check it, free of charge, with the verification_id or the phone number:
curl -X POST https://payperwa.com/api/v1/otp/verify \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"verification_id": "0b6f8f1e-6c1d-4f43-9a51-2f3c8f1d7a20",
"code": "483920"
}'
The answer has valid and a status: approved, incorrect (with attempts_remaining), max_attempts, expired or not_found. The OTP API reference lists every field.
Sending your own code. If your server generates and checks codes, send one request to POST https://payperwa.com/api/v1/messages/send and pass the code once, as the first item of variables. For an Authentication template, PayPerWA builds both the body parameter and the button parameter from that value, so you do not add the button parameter yourself:
curl -X POST https://payperwa.com/api/v1/messages/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "919876543210",
"template_name": "login_otp",
"language": "en",
"variables": ["483920"]
}'
A successful response on a channel where Meta bills you directly looks like this:
{
"success": true,
"data": {
"message_id": "wamid.HBgM...",
"status": "sent",
"cost": {
"meta_fee": 0,
"platform_fee": 0.2,
"wallet_deduction": 0.2,
"total_outlay": 0.2,
"billing_mode": "meta_direct",
"breakdown": "Meta bills customer directly + PayPerWA ₹0.20 fee",
"country": "IN",
"pricing_source": "meta_direct"
},
"otp_button_detection": "cache_copy_code"
}
}
A meta_fee of 0 means Meta's fee is not taken from your wallet; Meta bills it to the payment method on your WhatsApp Business account. The message_id matches the wamid in delivery and failure webhooks.
- Key. Create one under Dashboard → Settings → API. Keys start with
ppw_live_sk_and new keys include themessages:sendpermission, which both endpoints use. - Number format. Send
toas digits with the country code, no plus sign and no spaces. The Verify API also strips a leading plus or 00, spaces and dashes, and refuses a number that still starts with 0. Neither endpoint adds 91 for you. - Always pass the code on
/api/v1/messages/send. An Authentication send with novariablesattaches no button parameter. - Errors. 400 for validation or a send to your own number (
OWN_NUMBER), 401 for a bad key, 402 forINSUFFICIENT_WALLET_BALANCE, 404 for a template that is missing or not approved, 409META_API_OFFwhen your connected access token can no longer reach your WhatsApp Business account, 429 at the key's rate limit (100 requests a minute by default, verify calls included), and Meta's error indetailswhen Meta rejects the send. The Verify API adds 400OTP_TEMPLATE_REQUIREDorOTP_TEMPLATE_NOT_AUTHENTICATION; 429OTP_COOLDOWN,OTP_TOO_MANY_SENDSorOTP_TOO_MANY_FAILED_CHECKSwhen a number was sent a code less than 30 seconds ago, was sent 5 codes in the past hour or had 15 wrong codes entered in the past 24 hours, all checked before anything is charged; and 503OTP_UNAVAILABLE, with nothing sent or charged. A contact's opt-out never blocks an Authentication template, so it never blocks an OTP. - Retries. There is no idempotency key, so retrying a timed-out
/api/v1/messages/sendrequest can send and charge twice; add your own per-number resend cooldown on that path. The Verify API refuses a second code to the same number within 30 seconds without charging. Issue a fresh code for a resend either way.
Node.js, Python and PHP examples for both paths, plus webhook handling, are in our WhatsApp OTP login code examples; the WhatsApp OTP API page sums up the Verify API, and the other endpoints are in the API docs.
What an authentication message costs, and next steps
There is no subscription, setup fee or minimum monthly spend. Every OTP carries two separate charges, billed by two different parties, and checking a code with the Verify API is free:
- India: Meta ₹0.115 + PayPerWA ₹0.20 per authentication message. ₹0.115 is Meta's current India list rate for authentication, charged per delivered message, as shown on Meta's official pricing calculator on 8 November 2026.
- Other countries: PayPerWA's fee stays ₹0.20 per message on an INR wallet ($0.004 on a USD wallet), and Meta charges its own rate for the recipient's country. See the country rate card.
PayPerWA's fee comes from your prepaid wallet. Meta bills its own fee to the payment method on your WhatsApp Business account. If Meta rejects a send, or later reports it as failed, PayPerWA refunds its own fee to your wallet automatically.
Unless a free entry point window is open (one starts when you reply to someone who reached you through a Click-to-WhatsApp ad), Meta charges authentication messages even inside a 24-hour customer service window, and the 1,000 free service messages per number do not cover OTPs. Meta's volume discounts for India authentication start above 750,000 charged messages in a month, counted per business portfolio. Very high-volume senders messaging users in certain other countries can become eligible for Meta's higher authentication-international rates; see our WhatsApp OTP API in India guide. Meta can revise rates on 1 January, 1 April, 1 July and 1 October, so check the pricing page when you budget.
Before you go live, check that you have an approved Authentication template in each language you need, an expiry footer that matches your server's code lifetime, a 6-digit numeric code generated and checked by the Verify API or your server, the right template and language in every send, and a resend cooldown per number (built into the Verify API). To try it, you can sign up for PayPerWA, connect your WhatsApp Business number, create the template and send a test code to a phone other than the business number. There is no subscription and no DLT registration, the WhatsApp OTP API page shows the two calls, and the help centre is there if you get stuck.
Frequently Asked Questions
Can I change the text of a WhatsApp authentication template?
{{1}} is your verification code. in English), and URLs, media and emojis are not allowed. You can only switch on the optional security line and expiry footer, choose the button type and set a button label of up to 25 characters.Do one-tap autofill and zero-tap work on iPhone?
How long can a WhatsApp OTP code be?
Can I create one-tap or zero-tap templates in PayPerWA?
Does the user need to message my business before I can send an OTP?
Why was my OTP template in the Utility category rejected?
Does PayPerWA generate the OTP, and which template does it use?
template_name (add language if several templates share that name); a template of another category is refused. You can still send your own code in variables[0] with /api/v1/messages/send.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).
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.