A WhatsApp send is one POST carrying a recipient and exactly one kind of content: a template, or free-form text, image, video, audio, sticker, document, or location. The response is a 202 with a message ID, and delivery reports itself on webhooks you already handle.
import { BirdClient } from "@messagebird/sdk";
const bird = new BirdClient({
apiKey: process.env.BIRD_API_KEY!,
});
const msg = await bird.whatsapp.send({
to: "+15551234567",
template: {
slug: "bird_delivery_update",
components: [{ type: "body", parameters: [
{ type: "text", name: "ref", text: "#4821" },
{ type: "text", name: "date", text: "Wednesday" },
] }],
},
});
console.log(msg.id, msg.status);
// → "wam_01krdgeqcxet5s7t44vh8rt9mg", "accepted"The same send in every runtime.
Sending is the core verb of the Bird WhatsApp API. The SDKs cover Node.js, Python, Go, and PHP, and the CLI and raw HTTP take the same body. A first send can name a Bird-managed template, which picks its own sender, so nothing has to be provisioned before you watch a real message land.
const msg = await bird.whatsapp.send({
to: "+15551234567",
template: {
slug: "bird_otp",
components: [{ type: "body", parameters: [{ type: "text", text: "123456" }] }],
},
});
console.log(msg.id, msg.status);Exactly one content kind, never two.
A request carrying no content is refused with a 422, and so is one carrying two. A template is the only content WhatsApp delivers outside an open customer service window, which is why it is what starts a conversation. Free-form content is a reply, and it always names the number it comes from.
// A template: the only content deliverable outside an open window.
await bird.whatsapp.send({
to: "+15551234567",
template: { slug: "bird_delivery_update", components },
tags: [{ name: "campaign", value: "order-updates" }],
metadata: { order_id: "BRD-49217" },
});
// Free-form text: deliverable only inside one, and `from` is required.
await bird.whatsapp.send({
to: "+15551234567",
from: "+13124495648",
text: { body: "Your order shipped: https://example.com/track/A1B2C3", preview_url: true },
});
// An image, on its own send field. One content kind per request, never two.
await bird.whatsapp.send({
to: "+15551234567",
from: "+13124495648",
image: { url: "https://example.com/receipt.png", caption: "Your receipt" },
});
What every send carries.
The parts of the request that are not the message: who it is for, who it is from, how a retry behaves, and what rides back to you on every event.
- 01
A recipient, by number or by ID
An E.164 phone number, or the contact's Meta business-scoped user ID when you do not hold their number. One-time-passcode templates need a phone number.
- 02
A sender, unless the template picks one
Omit from for a Bird-managed template: its category selects the number. Everything else requires a from your workspace owns, on the same business account as the template.
- 03
An idempotency key, if you want one
A retry carrying the same Idempotency-Key replays the original response rather than sending twice. Without one, a retry is a new message and a duplicate.
- 04
Up to 20 tags
Structured name-value labels that become query dimensions: filter the message list by tag, and group metrics by one. Keep them low-cardinality.
- 05
Up to 2 KB of metadata
Arbitrary JSON stored on the message and returned on API reads and events. This is where an order ID or a foreign key belongs.
- 06
A 202, not a delivery
The response is the accepted message, echoing the content it resolved. Delivery happens afterwards, and reports itself on the message and its events.
A retry that does not double-send.
A timeout tells you nothing about whether the message went out. Send the retry with the same idempotency key and the API replays the original response instead of processing the request again, and marks the replay on the way back so your logs can tell the two apart.
curl -X POST https://api.bird.com/v1/whatsapp/messages \
-H "Authorization: Bearer $BIRD_API_KEY" \
-H "Idempotency-Key: order-shipped-BRD-49217" \
-H "Content-Type: application/json" \
-d '{
"to": "+15551234567",
"template": { "slug": "bird_delivery_update" }
}'
# A retry carrying the same key replays the original 202 and sets
# Idempotency-Replay: true. Without one, the recipient gets a duplicate.
Six events, and the two that mean failure.
Rejected means Bird refused the message before submitting it, so it is not charged. Failed means it was submitted and WhatsApp refused delivery. Both carry an error code, a description, and Meta's own code when there is one.
{
"type": "whatsapp.failed",
"timestamp": "2026-05-19T15:42:08.114Z",
"data": {
"whatsapp_id": "wam_01krdgeqcxet5s7t44vh8rt9mg",
"workspace_id": "ws_01krdgeqcxet5s7t44vh8rt9mg",
"direction": "outbound",
"from": { "phone_number": "+15557654321" },
"to": { "phone_number": "+15551234567" },
"tags": [{ "name": "campaign", "value": "order-updates" }],
"metadata": { "order_id": "BRD-49217" },
"error": {
"code": "service_window_expired",
"description": "The 24-hour service window closed; send a template.",
"occurred_at": "2026-05-19T15:42:08.031Z"
}
}
}
The delivered event is also when Meta's share of the price is charged, though no event payload carries a cost. Read the message back to see what it cost.
whatsapp.acceptedAccepted by the API and queued for send. This is what the 202 said.whatsapp.sentHanded to the WhatsApp network.whatsapp.deliveredWhatsApp confirmed the message reached the recipient's device.whatsapp.readThe recipient opened it. The status stays delivered; the read is recorded separately.whatsapp.rejectedRefused before submission, and not charged: reason code in the payload.whatsapp.failedSubmitted, then refused by WhatsApp: reason code in the payload.
One recipient per call.
There is no batch endpoint on WhatsApp: fan out in your own loop and pace against the rate-limit headers on each response rather than a constant you picked. Sends are metered per message, and pricing moves with the destination country and the template's category.
Go deeper in the docs.
Read sending WhatsApp for the full request contract, WhatsApp events for the lifecycle and its payloads, and webhooks for endpoints, signatures, and retries.
Sending questions, answered.
The request, retries, tags and metadata, and delivery status.
Como envio uma mensagem de WhatsApp?
O que acontece se eu reenviar após um timeout?
Posso anexar tags ou metadados a uma mensagem?
Como sei se uma mensagem foi entregue?
The rest of the WhatsApp platform
One API, one set of keys. Explore the other capabilities.
Your first WhatsApp send, today.
Sending is one capability of the Bird WhatsApp API: templates, inbound, numbers, and analytics ship with it, on infrastructure we have run for a decade.