WhatsApp authentication templates
An authentication template delivers a one-time passcode. It is the only category that may carry one: WhatsApp writes the body copy itself, and the send takes flags instead of text.
Before you send
Sending and authoring sit behind two different accounts, and they are not gated the same way.
Sending Bird's stocked catalog templates, bird_otp and bird_otp_authifly, needs no verification of yours. Those templates live on Bird's own WhatsApp Business Accounts, and the managed send path never checks your business's verification status.
Authoring your own passcode template, or copying an existing one onto your account, does require a verified business. Bird checks this before the write and refuses with 412 E15043 WhatsAppTemplateBusinessNotVerified if your business isn't verified, whether you're creating a template from scratch or duplicating one onto your account. See WhatsApp business verification for how verification works and how to submit your business to Meta. Once Meta verifies you, allow a few hours: Bird re-reads the verdict on a sync schedule rather than live.
Utility and marketing template authoring is unaffected by this gate; you can keep creating and editing those regardless of your verification status.
Author templates in the dashboard, with the bird CLI, or through the MCP server. See Authoring WhatsApp templates for the full flow.
Sending a passcode
POST /v1/whatsapp/messages with a template object naming a catalog slug:
const msg = await bird.whatsapp.send({
to: "+14155550100",
template: {
slug: "bird_otp",
language: "en",
components: [{ type: "body", parameters: [{ type: "text", text: "481920" }] }],
},
});
console.log(msg.id, msg.status);msg = client.whatsapp.send(
to="+14155550100",
template="bird_otp",
language="en",
components=[{"type": "body", "parameters": [{"type": "text", "text": "481920"}]}],
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
code := "481920"
components := []bird.WhatsAppMessageTemplateComponent{{
Type: "body",
Parameters: &[]bird.WhatsAppMessageTemplateComponentParameter{{Type: "text", Text: &code}},
}}
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+14155550100",
Template: "bird_otp",
Language: "en",
Components: components,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$components = [
(new WhatsAppMessageTemplateComponent())
->setType('body')
->setParameters([
(new WhatsAppMessageTemplateComponentParameter())->setType('text')->setText('481920'),
]),
];
$message = $bird->whatsapp->send(
to: '+14155550100',
template: 'bird_otp',
language: 'en',
components: $components,
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--to +14155550100 \
--template bird_otp \
--language en \
--components '[{"type":"body","parameters":[{"type":"text","text":"481920"}]}]'curl -X POST "https://us1.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $BIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+14155550100",
"template": {
"slug": "bird_otp",
"language": "en",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "481920"
}
]
}
]
}
}'Four rules are specific to this category:
- Omit from. A Bird-managed template picks its own sender, from the category and region, so setting from returns 422 E15018 WhatsAppSenderNotAllowed. That's the opposite of a free-form send, which requires from, worth remembering if you're arriving here from the interactive-message pages.
- to must be an E.164 phone number. An authentication template can't go to a business-scoped user ID; that's 422 E15014 WhatsAppRecipientNotSupportedForTemplate.
- The body takes exactly one positional parameter, the code. Zero parameters, or naming the parameter, returns 422 E15003 WhatsAppTemplateParameterMismatch. Authentication is the one category Meta writes positionally; every other category names its parameters.
- No customer service window is needed. Template sends aren't window-gated, which is the whole reason a passcode template exists: it has to reach someone who has never messaged you before.
bird_otp and bird_otp_authifly stock 70 languages each. If the language you request isn't stocked, the send fails rather than substituting another language.
The copy code button
Meta writes an authentication template's body itself, as a preset carrying a single code placeholder, so you supply flags rather than text. The button component is optional in a send: if you don't include one, Bird adds it for you, carrying the same code as the body. You can also supply it yourself:
Esempio di codice
{ "type": "button", "parameters": [{ "type": "text", "text": "481920" }] }Either way, exactly one button reaches WhatsApp, and it's the copy-code button: tapping it copies the code to the clipboard. Bird supports copy_code only; the other two button behaviors Meta documents for authentication templates, one-tap and zero-tap autofill, aren't available on Bird today.
Authoring a template's button follows the same shape: one otp button, and the template takes no other button type. You supply add_security_recommendation (a boolean shown in the body) and code_expiration_minutes (1 to 90, shown in the footer) instead of writing copy.
What Meta allows in an authentication template
Meta fixes an authentication template's structure and reviews its content: no URLs, media, or emojis anywhere in the template, and a 15-character cap on the code parameter. The category also changes how WhatsApp delivers the message, sending it only to the recipient's primary device. See Template guidelines for the fixed structure, character limits, and review process in full.
Cost
Category and destination set the price. See WhatsApp authentication-international rates for how sending to a country other than your primary location can change it, and Cost and billing for when a send is charged. Rate numbers live on WhatsApp pricing.
Things to watch
- An authored send requires from, and it has to sit on the same WhatsApp Business Account as the template. A sender on a different account is refused 422 E15023 WhatsAppSenderWABAMismatch before anything is charged.
- Only a language whose version is approved and live is sendable. A draft, pending, rejected, or paused language isn't.
- Meta can recategorize a template on its own initiative. There's no opt-out, and it moves the pricing and delivery rules that follow the category.
- The template's category and its language's category can diverge. See WhatsApp templates for how the send path resolves this.
- An ingested authentication template can't be duplicated. Bird can't read WhatsApp's generated copy back into the settings a new template is built from; that's 422 E15024 WhatsAppTemplateContentNotDuplicable. Create a new one with its own security recommendation and code expiration instead.
Next steps
- WhatsApp templates: browsing the catalog and the shared send-by-template contract
- Utility templates: order updates, appointment reminders, and account notices
- WhatsApp business verification: how verification works and what else it unlocks