Sending

One call out. One kind of content.

Set up in:
Cursor

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.

send-notification.ts
202 · 480ms
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"
Reminder: you have an appointment on 3 Sep at 14:30. We look forward to seeing you.9:42 AM
Reschedule
Your order #4821 is out for delivery, arriving Wednesday. Thanks for shopping with us.9:43 AM
Your subscription renews on 3 Sep for €12.00. No action is needed.9:44 AM
View plan

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.

1
2
3
4
5
6
7
8
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.

send.ts
202 · accepted
// 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

retry.sh
Idempotency-Key
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.

POST /webhooks/bird
signed
{
  "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.

Jak wysłać wiadomość WhatsApp?
Wyślij POST na /v1/whatsapp/messages z numerem telefonu odbiorcy w formacie E.164, slugiem szablonu i wartościami zmiennych szablonu. Bird waliduje żądanie, zwraca 202 z identyfikatorem wiadomości i dostarcza ją asynchronicznie.
Co się stanie, jeśli ponowię wysyłkę po przekroczeniu limitu czasu?
Dodaj nagłówek Idempotency-Key, a ponowione żądanie zwróci oryginalny wynik zamiast wysyłać wiadomość ponownie. Bez niego ponowna próba jest traktowana jako nowa wiadomość, a odbiorca otrzymuje duplikat.
Czy mogę dodać tagi lub metadane do wiadomości?
Tak. Tagi to maksymalnie 20 ustrukturyzowanych etykiet, według których możesz filtrować i grupować dane w dzienniku wiadomości i metrykach. Metadane to dowolny JSON (do 2 KB) zwracany wraz z wiadomością i jej zdarzeniami, przydatny do korelowania wysyłek z Twoimi własnymi systemami.
Skąd wiem, czy wiadomość została dostarczona?
Każda zmiana stanu generuje zdarzenie webhook: accepted, sent, delivered, read, failed lub rejected. Możesz też odpytywać oś czasu zdarzeń wiadomości przez API. Status delivered oznacza, że WhatsApp potwierdził odebranie wiadomości przez urządzenie odbiorcy.

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.

Zacznij od jednego kanału.
Dodaj kolejne, gdy będziesz gotowy.

Testowy klucz API otrzymasz od razu. Dostęp produkcyjny odblokujesz po dodaniu metody płatności i weryfikacji nadawcy.

Używasz Claude Code, Cursor lub Codex? Skopiuj prompt konfiguracyjny, a Twój agent zainstaluje za Ciebie Bird CLI i umiejętności. Wybierz swój:

Cursor