# 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`](/docs/api/errors/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](/docs/knowledge-base/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](/docs/cli), or through the [MCP server](/docs/ai/mcp-server). See [Authoring WhatsApp templates](/docs/guides/whatsapp/authoring-templates) for the full flow.

## Sending a passcode

`POST /v1/whatsapp/messages` with a `template` object naming a catalog slug:

<!-- bird:tabs typescript,python,go,php,cli,curl -->

```typescript
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);
```

```python
msg = client.whatsapp.send(
    to="+14155550100",
    template="bird_otp",
    language="en",
    components=[{"type": "body", "parameters": [{"type": "text", "text": "481920"}]}],
)
print(msg.id, msg.status)
```

```go
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)
}
```

```php
$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();
```

```cli
bird whatsapp send \
  --to +14155550100 \
  --template bird_otp \
  --language en \
  --components '[{"type":"body","parameters":[{"type":"text","text":"481920"}]}]'
```

```curl
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"
              }
            ]
          }
        ]
      }
    }'
```

<!-- /bird:tabs -->

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`](/docs/api/errors/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`](/docs/api/errors/E15014) `WhatsAppRecipientNotSupportedForTemplate`.
- **The body takes exactly one positional parameter, the code.** Zero parameters, or naming the parameter, returns `422` [`E15003`](/docs/api/errors/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:

```json
{ "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](/docs/knowledge-base/whatsapp/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](/docs/knowledge-base/whatsapp/authentication-international-rates) for how sending to a country other than your primary location can change it, and [Cost and billing](/docs/guides/whatsapp/sending-whatsapp#cost-and-billing) for when a send is charged. Rate numbers live on [WhatsApp pricing](https://bird.com/pricing/whatsapp).

## 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`](/docs/api/errors/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](/docs/guides/whatsapp/templates#listing-templates-from-the-api) 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`](/docs/api/errors/E15024) `WhatsAppTemplateContentNotDuplicable`. Create a new one with its own security recommendation and code expiration instead.

## Next steps

- [WhatsApp templates](/docs/guides/whatsapp/templates): browsing the catalog and the shared send-by-template contract
- [Utility templates](/docs/guides/whatsapp/templates/utility): order updates, appointment reminders, and account notices
- [WhatsApp business verification](/docs/knowledge-base/whatsapp/business-verification): how verification works and what else it unlocks