WhatsApp utility templates
A utility template follows up on something the recipient already did: an order, a payment, a booking, a sign-in. Bird's catalog stocks eight of them, including bird_signin_alert and bird_delivery_update. Take a slug's category from the template list rather than its name: bird_signin_alert reads like an authentication template and isn't one, it's utility.
Before you send
Utility has the fewest prerequisites of the three categories.
Sending Bird's stocked catalog templates needs no verification of yours, the same as authentication. Authoring a utility template of your own needs none either: unlike authentication, Meta's business-verification gate never applies to utility, so you can create and edit utility templates on an unverified workspace. See WhatsApp business verification for what verification unlocks elsewhere.
to can be an E.164 phone number or a business-scoped user ID. A utility template carries no OTP button, so it doesn't require the phone-number-only recipient that authentication does.
Every managed catalog utility template is registered in en only, with on_missing_language: fail. Requesting a language the catalog doesn't stock fails the send rather than falling back to English or anything else.
Sending a utility template
POST /v1/whatsapp/messages with a template object naming a catalog slug:
const msg = await bird.whatsapp.send({
to: "+16505551234",
template: {
slug: "bird_order_confirmation",
language: "en",
components: [
{
type: "body",
parameters: [
{ type: "text", name: "ref", text: "A1B2C3D4" },
{ type: "text", name: "amount", text: "USD 49.99" },
],
},
],
},
});
console.log(msg.id, msg.status);msg = client.whatsapp.send(
to="+16505551234",
template="bird_order_confirmation",
language="en",
components=[
{
"type": "body",
"parameters": [
{"type": "text", "name": "ref", "text": "A1B2C3D4"},
{"type": "text", "name": "amount", "text": "USD 49.99"},
],
}
],
)
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)
}
ref := "A1B2C3D4"
amount := "USD 49.99"
refName := "ref"
amountName := "amount"
components := []bird.WhatsAppMessageTemplateComponent{{
Type: "body",
Parameters: &[]bird.WhatsAppMessageTemplateComponentParameter{
{Type: "text", Name: &refName, Text: &ref},
{Type: "text", Name: &amountName, Text: &amount},
},
}}
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+16505551234",
Template: "bird_order_confirmation",
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')->setName('ref')->setText('A1B2C3D4'),
(new WhatsAppMessageTemplateComponentParameter())->setType('text')->setName('amount')->setText('USD 49.99'),
]),
];
$message = $bird->whatsapp->send(
to: '+16505551234',
template: 'bird_order_confirmation',
language: 'en',
components: $components,
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--to +16505551234 \
--template bird_order_confirmation \
--language en \
--components '[{"type":"body","parameters":[{"type":"text","name":"ref","text":"A1B2C3D4"},{"type":"text","name":"amount","text":"USD 49.99"}]}]'curl -X POST "https://us1.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $BIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+16505551234",
"template": {
"slug": "bird_order_confirmation",
"language": "en",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"name": "ref",
"text": "A1B2C3D4"
},
{
"type": "text",
"name": "amount",
"text": "USD 49.99"
}
]
}
]
}
}'As with any managed template, omit from: Bird picks the sending number from the category and region, and setting it returns 422 E15018 WhatsAppSenderNotAllowed. Authoring your own utility template and sending it works the same way as any authored send; see Sending with a template for the general contract.
Filling the variables
Utility parameters are named, the reverse of authentication's single positional code. Every parameter carries a name, and a named parameter's order in the array carries no meaning. Send one components entry for each block that actually has a placeholder; a body with no variables takes no components entry for it at all.
A URL button is the one exception: its variable is always positional {{1}}, and the send carries the bare value rather than a full address:
Exemplo de código
{ "type": "button", "parameters": [{ "type": "text", "text": "A-4192" }] }For the shared rules on components, sub_type, and how a send's components line up with a template's declared placeholders, see Sending with a template and Components and parameters.
Cost
A utility template delivered inside an open customer service window is free on Meta's side of the invoice. It is not free on Bird's: Bird's own accept-time fee is charged regardless of the window, because that charge is set before delivery, when Meta's verdict isn't known yet. Treat "utility in-window is free" as only half the story.
See Cost and billing for when a send is charged, and WhatsApp pricing for rates.
Things to watch
- Meta can recategorize a utility template as marketing on its own initiative, and the message keeps sending at the new, higher price. A business Meta has already warned for miscategorization gets no advance notice at all since April 2025; the change lands instantly. Keep promotional language, offers, or upsells out of a utility template's copy, since that's what triggers the move. See Template guidelines for what reads as promotional.
- A gif header or a copy_code button is refused outside marketing. Both are marketing-only components; declaring either on a utility template fails.
- An authored send isn't checked for parameter count before it's charged. Send the wrong number of parameters on a template of your own and the message is accepted and charged, then rejected by Meta. Managed catalog sends don't have this gap.
- A sender on the wrong WhatsApp Business Account is refused before any charge. from has to sit on the same account as the template; otherwise the send fails 422 E15023 WhatsAppSenderWABAMismatch.
Next steps
- WhatsApp templates: browsing the catalog and the shared send-by-template contract
- Authentication templates: one-time passcodes and the verification gate on authoring one
- Marketing templates: promotional sends and the account you need to author one