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.

Bagaimana cara mengirim pesan WhatsApp?
POST ke /v1/whatsapp/messages dengan nomor telepon E.164 penerima, slug template, dan nilai untuk variabel template. Bird memvalidasi permintaan, mengembalikan 202 dengan ID pesan, dan mengirimkan secara asinkron.
Apa yang terjadi jika saya mengulangi pengiriman setelah timeout?
Sertakan header Idempotency-Key dan permintaan yang diulang akan mengembalikan hasil asli alih-alih mengirim dua kali. Tanpa header tersebut, pengulangan dianggap sebagai pesan baru dan penerima mendapat duplikat.
Bisakah saya melampirkan tag atau metadata ke pesan?
Ya. Tag adalah hingga 20 label terstruktur yang dapat Anda filter dan kelompokkan di log pesan dan metrik. Metadata adalah JSON arbitrary (hingga 2 KB) yang dikembalikan pada pesan dan event-nya, berguna untuk menghubungkan pengiriman dengan sistem Anda sendiri.
Bagaimana cara mengetahui apakah pesan sudah terkirim?
Setiap perubahan status memicu event webhook: accepted, sent, delivered, read, failed, atau rejected. Anda juga dapat melakukan polling timeline event pesan melalui API. Status delivered berarti WhatsApp mengonfirmasi perangkat penerima telah menerimanya.

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.

Mulai dengan satu channel.
Tambahkan yang lain saat Anda siap.

API key uji coba langsung tersedia untuk Anda. Akses produksi terbuka setelah Anda menambahkan metode pembayaran dan memverifikasi pengirim.

Menggunakan Claude Code, Cursor, atau Codex? Salin prompt pengaturan dan agen Anda akan menginstal Bird CLI dan skill untuk Anda. Pilih milik Anda:

Cursor