Mengirim pesan WhatsApp
Panduan ini membahas endpoint pengiriman, POST /v1/whatsapp/messages. Anda menyusun satu payload JSON dengan satu penerima dan tepat satu jenis konten: template yang telah disetujui, atau pesan layanan berisi teks, gambar, video, audio, stiker, dokumen, lokasi, kartu kontak, atau sesuatu yang bisa diketuk. Bird mengembalikan 202 Accepted dengan ID pesan dan mengirim secara asinkron. Jenis mana yang bisa Anda kirim bergantung pada jendela layanan pelanggan. Setiap permintaan mengirim satu pesan ke satu penerima, dan tidak ada endpoint batch.
Pengiriman minimal
Payload valid terkecil adalah penerima to dan template dengan slug-nya. Tambahkan language jika Anda menginginkan bahasa tertentu; tidak menyertakannya akan mengirim bahasa default template, dan isi variabel yang dideklarasikan template melalui components.
Pemanggilan curl menyebutkan host US; jika kunci Anda diawali dengan bk_eu1_, panggil https://eu1.platform.bird.com sebagai gantinya. SDK membaca region dari kunci Anda, jadi tidak perlu mengatur host.
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);msg = client.whatsapp.send(
to="+31612345678",
template="bird_otp",
language="en",
components=[{"type": "body", "parameters": [{"type": "text", "text": "123456"}]}],
)
print(msg.id, msg.status)code := "123456"
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+15551234567",
Template: "bird_otp",
Language: "en",
Components: []bird.WhatsAppMessageTemplateComponent{{
Type: "body",
Parameters: &[]bird.WhatsAppMessageTemplateComponentParameter{{Type: "text", Text: &code}},
}},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)$message = $bird->whatsapp->send(
to: '+15551234567',
template: 'bird_otp',
language: 'en',
components: [
(new WhatsAppMessageTemplateComponent())
->setType('body')
->setParameters([
(new WhatsAppMessageTemplateComponentParameter())->setType('text')->setText('123456'),
]),
],
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--components '[{"parameters":[{"text":"1234","type":"text"}],"type":"body"},{"parameters":[{"text":"1234","type":"text"}],"type":"button"}]' \
--language en \
--template bird_otp \
--to +31612345678curl -X POST "https://{region}.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+31612345678",
"template": {
"slug": "bird_otp",
"language": "en",
"components": [
{
"type": "body",
"parameters": [
{
"type": "text",
"text": "1234"
}
]
},
{
"type": "button",
"parameters": [
{
"type": "text",
"text": "1234"
}
]
}
]
}
}'Jendela layanan pelanggan
Jenis mana yang bisa Anda kirim bergantung pada satu kondisi: apakah jendela layanan pelanggan sedang terbuka.
Kontak membuka jendela layanan dengan mengirim pesan atau menelepon nomor bisnis Anda, dan jendela layanan tetap terbuka selama 24 jam, direset setiap kali mereka mengirim pesan lagi. Selama terbuka, Anda dapat mengirim pesan layanan, yaitu konten bebas apa pun: teks, gambar, video, audio, stiker, dokumen, lokasi, atau interaktif. Setelah kedaluwarsa, hanya template yang telah disetujui yang dapat menjangkau mereka, dan balasan mereka terhadap template tersebut membuka kembali jendela layanan.
Bird melacak jendela layanan untuk Anda, sehingga pesan layanan yang dikirim ke jendela layanan yang tertutup ditolak sebelum apa pun dibuat atau dikenakan biaya: permintaan mengembalikan 422 E15044 WhatsAppServiceWindowClosed. Pemeriksaan ini dilakukan sebisa mungkin dan tetap meloloskan permintaan jika gagal, jadi 202 bukan bukti bahwa jendela layanan benar-benar terbuka saat pengiriman. Jika jendela layanan kedaluwarsa antara penerimaan permintaan dan pengiriman, pesan gagal secara asinkron dengan service_window_expired pada last_error pesan.
Lihat jendela layanan pelanggan untuk siklus lengkapnya: apa yang membukanya, apa yang meresetnya, dan bagaimana interaksinya dengan harga.
Menyusun payload
Penerima
to menyebutkan satu tujuan, berupa nomor telepon, ID pengguna cakupan bisnis, atau ID grup. Nomor telepon menggunakan format E.164: diawali +, kode negara, dan nomor pelanggan, misalnya +14155550100. Kami memvalidasi nomor tersebut, sehingga nilai yang bukan nomor nyata dan dapat dihubungi (panjang salah, prefiks tidak terdaftar) ditolak dengan 422 WhatsAppInvalidRecipient sebelum ada biaya yang dikenakan. Tidak ada array penerima dan tidak ada pengiriman batch, jadi untuk menjangkau banyak orang yang tidak berada dalam satu grup, Anda perlu satu panggilan per penerima.
ID pengguna cakupan bisnis seperti US.13491208655302741918 digunakan untuk mengirim pesan ke kontak yang nomor teleponnya tidak Anda miliki, yaitu cara Anda membalas kontak yang menghubungi Anda tanpa nomor telepon. Dua hal berubah: nomor pengirim harus termasuk dalam portofolio bisnis yang sama dengan cakupan ID tersebut, dan template kode verifikasi sekali pakai memerlukan nomor telepon. Template kode verifikasi sekali pakai yang dikelola Bird ditolak saat permintaan diterima dengan 422 WhatsAppRecipientNotSupportedForTemplate; template autentikasi yang dibuat workspace Anda diterima lalu gagal, karena Meta memerlukan nomor telepon untuk template tersebut.
to menerima satu bentuk lagi: ID grup WhatsApp seperti wag_01krdgeqcxet5s7t44vh8rt9mg, yang mengirim ke setiap peserta chat grup tersebut. Pengiriman grup tidak menyertakan from dan melaporkan pengirimannya untuk seluruh grup, bukan untuk satu penerima, sehingga Mengirim ke grup WhatsApp membahasnya di halaman tersendiri.
Template
template menyebutkan template yang telah disetujui untuk dikirim:
slug(wajib): slug template, misalnyabird_order_confirmation. Harus cocok dengan template di katalog Anda (huruf kecil, digit, dan garis bawah).language: tag bahasa template, misalnyaenataupt-BR. Abaikan untuk mengirim bahasa default template; menyebutkan bahasa yang tidak dimiliki template mengembalikan422yang mencantumkan bahasa yang tersedia. Pesan yang diterima menampilkan bahasa yang diterapkan.components: nilai yang mengisi variabel template (lihat Komponen dan parameter). Abaikan untuk template tanpa variabel.
Telusuri template Anda, bahasanya, dan pratinjau render masing-masing di halaman Templates.
Komponen dan parameter
Template memiliki variabel, baik bernama ({{ref}}, {{amount}}) maupun bernomor ({{1}}, {{2}}). Anda mengisi nilainya melalui components. Setiap komponen menyebutkan type (body atau button) dan array parameters. Setiap parameter menyebutkan type-nya sendiri (text, image, video, gif, document, atau location) dan membawa field yang sesuai: text berupa string biasa, image/video/gif/document berupa https url publik, dan location berupa titik di peta. Template dengan parameter bernama memerlukan name pada setiap parameter, yang harus cocok persis dengan nama yang dideklarasikan template (lihat Referensi field). Template posisional tidak menyertakan name dan mengambil nilainya sesuai urutan {{n}}, sehingga parameter pertama mengisi {{1}}. Bagaimanapun, parameter yang tidak cocok dengan deklarasi template mengembalikan 422 WhatsAppTemplateParameterMismatch. Tipe komponen header juga ada di wire: pada template yang dikelola Bird, tipe ini diabaikan karena tidak ada template yang dikelola Bird yang mendeklarasikan variabel header, tetapi pada template yang dibuat workspace Anda, tipe ini diteruskan, dan begitulah cara template utilitas atau marketing dengan media header mendapatkan gambarnya.
Misalnya, template kode verifikasi sekali pakai yang isinya bertuliskan {{1}} is your verification code dan tombolnya menyalin kode menerima kode tersebut sebagai parameter isi sekaligus parameter tombol secara posisional (tanpa name):
{
"components": [
{ "type": "body", "parameters": [{ "type": "text", "text": "481920" }] },
{ "type": "button", "parameters": [{ "type": "text", "text": "481920" }] }
]
}Kategori dan pengirim
Kategori template (authentication, utility, atau marketing) menentukan cara WhatsApp memperlakukan pesan dan, bersama negara tujuan, berapa biayanya.
Siapa pemilik pengirim menentukan apakah Anda perlu menyebutkannya:
- Template yang dikelola Bird (slug-nya diawali
bird_) dikirim dari nomor yang disimpan Bird untuk kategori tersebut, jadi abaikanfrom. Mengisinya mengembalikan422WhatsAppSenderNotAllowed. - Semua yang lain menyebutkan pengirimnya sendiri di
from: pesan layanan jenis apa pun, dan template apa pun yang dibuat workspace Anda. Nomor tersebut harus dimiliki workspace Anda. Mengabaikannya mengembalikan422WhatsAppSenderRequired, dan nomor yang tidak dapat digunakan workspace untuk mengirim mengembalikan422WhatsAppSenderNotFound. Template buatan workspace juga harus berada di WhatsApp Business Account yang sama dengan nomornya; jika tidak, pengiriman mengembalikan422WhatsAppSenderWABAMismatch.
Pengaturan nomor telepon membahas kedua jenis nomor dan cara menghubungkan nomor milik Anda.
Pesan layanan
Alih-alih template, sertakan tepat satu dari text, image, video, audio, sticker, document, location, contact_cards, atau interactive. Kesembilan jenis ini adalah pesan layanan, sehingga memerlukan jendela layanan pelanggan yang terbuka. Semuanya juga memerlukan from, nomor yang dimiliki workspace Anda; nomor yang dikelola Bird tidak dapat digunakan.
text:{ "body": "..." }, hingga 4096 karakter. Tambahkan"preview_url": trueuntuk merender pratinjau tautan dari URL pertama dibody.image,video,audio,sticker,document: masing-masing menerima URLhttpspublik yang diambil WhatsApp saat pengiriman (url), jadi URL bertanda tangan harus tetap berlaku lebih lama dari pengiriman. URLhttplangsung ditolak. WhatsApp mengambil file itu sendiri, sehingga URL yang tidak dapat dijangkau, yang menyajikan tipe tidak didukung, atau file yang melebihi batas ukuran untuk tipenya akan diterima lalu gagal, denganmedia_rejectedpadalast_errorpesan dan alasan dari WhatsApp sendiri didescription.image,video, dandocumentjuga menerimacaptionopsional;documentjuga menerimafilenameopsional;audiomenerima flagvoiceopsional untuk rendering voice note.location:{ "latitude": ..., "longitude": ... }(keduanya wajib, derajat desimal) ditambahnamedanaddressopsional.contact_cards: array berisi hingga lima kontak yang dibagikan dalam satu pesan.namesetiap kartu memerlukanformatted_nameditambah setidaknya satu bagian lain (first_name,last_name,middle_name,prefix, atausuffix);phone_numbers,emails,urls, danaddressesmasing-masing menerima hingga sepuluh entri, danorgsertabirthday(sebagaiYYYY-MM-DD) bersifat opsional.phone_numberdalam format E.164 memberi kartu tersebut tombol yang membuka chat dengannya.interactive: teks body ditambah sesuatu untuk diketuk, dalam salah satu dari enam tipe: tombol balasan, menu daftar, tombol tautan, carousel media, atau tombol tunggal yang meminta lokasi atau nomor telepon penerima. Pesan interaktif membahas bentuk wire setiap tipe, balasan yang dihasilkan ketukan, dan batasannya.
{
"to": "+16505551234",
"from": "+13124495648",
"text": { "body": "Your order shipped: https://example.com/track/A1B2C3", "preview_url": true }
}Permintaan tanpa konten, atau dengan lebih dari satu tipe, ditolak dengan 422.
Mengutip pesan
Atur in_reply_to_message_id ke ID pesan WhatsApp untuk mengirim pesan Anda sebagai balasan atas pesan tersebut, seperti saat Anda mengetuk balas di aplikasi WhatsApp untuk mengutip pesan. Penerima melihat pesan Anda dengan pesan yang dikutip di atasnya, dan field ini dikembalikan setiap kali pesan dibaca.
Ini juga berlaku sebaliknya: pesan masuk yang ditandai WhatsApp sebagai balasan membawa ID pesan yang dikutip di field yang sama, dan begitulah cara Anda mengetahui pesan mana yang dibalas. Pesan masuk yang tidak ditandai WhatsApp tidak membawa ID, dan resolusi juga bisa meleset. Untuk korelasi yang andal, gunakan identifier balasan interaktif eksplisit bersama status percakapan atau tugas yang disimpan aplikasi Anda. metadata keluar tetap di catatan keluar dan tidak otomatis disalin ke balasan.
Kutipan diselesaikan sebelum pengiriman diterima, jadi kutipan yang tidak dapat dirender menggagalkan permintaan itu sendiri dan tidak ada yang dibuat atau dikenakan biaya. ID yang tidak merujuk pesan mana pun di workspace ini, atau yang lebih lama dari 15 hari masa pesan dapat dikutip, mengembalikan 404 E15071 WhatsAppReferencedMessageNotFound. ID yang merujuk pesan yang tidak pernah sampai ke WhatsApp, atau pesan dari percakapan berbeda dari to dan from pengiriman ini, mengembalikan 422 E15072 WhatsAppMessageNotQuotable. Jika Bird tidak dapat menjangkau penyimpanan yang menjawab pertanyaan tersebut, pengiriman mengembalikan 503 E15073 WhatsAppMessageLookupUnavailable, yang layak untuk dicoba lagi. Pengutipan berfungsi pada pengiriman template maupun pengiriman free-form.
{
"to": "+16505551234",
"from": "+13124495648",
"in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
"text": { "body": "Yes, that slot is still free." }
}Tag dan metadata
Dua field opsional melampirkan konteks Anda sendiri ke pesan; keduanya dikembalikan pada pembacaan API dan disertakan pada setiap event webhook untuk pesan tersebut:
tags: hingga 20 label{ "name": ..., "value": ... }terstruktur untuk dimensi kardinalitas rendah yang Anda gunakan untuk memfilter dan melaporkan (kampanye, varian eksperimen). Nama dan nilai menerima huruf ASCII, digit, garis bawah, dan tanda hubung; nama dibatasi 32 karakter dan unik dalam satu pengiriman, nilai dibatasi 64. Filter daftar pesan berdasarkan tag (?tag=campaignatau?tag=campaign:launch-week), dan halaman Metrics memecah pengiriman berdasarkan tag.metadata: satu objek JSON arbitrer, hingga 2 KB saat diserialisasi, untuk konteks per pengiriman yang tidak Anda perlukan sebagai dimensi filter (ID pesanan internal, referensi sesi).
{
"tags": [{ "name": "campaign", "value": "order-confirmations" }],
"metadata": { "order_id": "ord_8271" }
}Referensi field
| Field | Tipe | Wajib | Batasan / catatan |
|---|---|---|---|
to | string | ya | Satu penerima per pesan: nomor telepon E.164, ID pengguna cakupan bisnis, yang tidak diterima template kode verifikasi sekali pakai, atau group ID WhatsApp (wag_…), yang mengirim ke setiap peserta grup tersebut |
from | string (E.164) | tidak** | Abaikan untuk template yang dikelola Bird, yang memilih pengirimnya sendiri, dan untuk pengiriman grup, yang menggunakan nomor grup itu sendiri; wajib untuk pesan layanan dan template yang dibuat workspace Anda, dan harus nomor yang dimiliki workspace Anda |
template.slug | string | tidak** | Slug template yang dapat dikirim workspace Anda; slug yang dikelola Bird diawali dengan bird_ |
template.language | string | tidak* | Tag bahasa template (en, pt-BR); abaikan untuk mengirim bahasa default template |
template.components | array | tidak | Mengisi variabel template; type komponen adalah body atau button |
template.components[].parameters[].name | string | tidak† | Placeholder yang diisi nilai ini, misalnya ref; wajib dan harus cocok dengan nama yang dideklarasikan template untuk template parameter bernama, diabaikan untuk template posisional |
interactive | object | tidak** | Teks body ditambah satu tipe konten yang dapat diketuk; merupakan pesan layanan, jadi memerlukan jendela layanan pelanggan yang terbuka. Lihat Pesan interaktif |
in_reply_to_message_id | string | tidak | Message ID WhatsApp yang dimiliki workspace ini, dikutip dalam pesan yang Anda kirim; dikembalikan saat dibaca. Lihat Mengutip pesan |
tags | array | tidak | Hingga 20 label {name, value}; nama ≤ 32 karakter, nilai ≤ 64, nama unik |
metadata | object | tidak | JSON arbitrer, hingga 2 KB saat diserialisasi |
* language bersifat opsional; jika tidak disertakan, bahasa default template yang dikirim.
† name wajib pada setiap parameter untuk template parameter bernama. Abaikan untuk template posisional. Lihat Komponen dan parameter.
** Sertakan tepat satu dari template atau field konten pesan layanan (text, image, video, audio, sticker, document, location, interactive); lihat Pesan layanan.
Model async: arti 202
Pengiriman yang berhasil mengembalikan 202 Accepted dengan ID pesan dan status: accepted. 202 dikembalikan hanya setelah pengiriman diterima secara permanen; pengiriman tidak pernah diterima lalu dihapus secara diam-diam. Kegagalan fatal yang bisa Anda perbaiki langsung gagal dengan 422: penerima tidak valid, slug atau bahasa template tidak dikenal, ketidakcocokan parameter, atau pesan layanan yang dikirim ke jendela layanan pelanggan yang sudah tertutup (WhatsAppServiceWindowClosed). Saldo dompet kosong bukan salah satunya: pengiriman tetap diterima, dan pesan berakhir rejected dengan insufficient_balance setelah Bird mencoba menagihnya. Pengiriman aktual terjadi secara asinkron: pesan berpindah ke sent saat kami menyerahkannya ke WhatsApp, lalu ke status terminal (delivered atau failed) saat tanda terima tiba, dilaporkan melalui events, webhooks, dan endpoint baca. Tanda terima dibaca dimunculkan secara terpisah sebagai timestamp read_at dan event whatsapp.read, bukan sebagai status.
Satu catatan privasi: untuk template kategori authentication, API tidak pernah mengembalikan nilai yang telah diisi. Echo 202 dan setiap pembacaan berikutnya membawa array components kosong untuk pesan-pesan tersebut, sehingga kode verifikasi tidak pernah muncul kembali.
Mencoba lagi dengan aman
Kirim header Idempotency-Key dengan nilai unik per pengiriman logis, dan percobaan ulang menjadi aman. Jika permintaan pertama Anda berhasil tetapi Anda tidak melihat responsnya (timeout, koneksi terputus), mengulangnya dengan kunci yang sama mengembalikan hasil asli alih-alih mengirim, dan menagih, pesan duplikat. Respons yang diulang membawa header Idempotency-Replay. Lihat idempotency untuk format kunci dan retensi.
Menerima balasan
Pesan masuk tersimpan di resource yang sama dengan pesan keluar, dan setiap pesan masuk mereset jendela layanan pelanggan. Menerima pesan WhatsApp membahas cara membacanya melalui API, mengambil media yang dikirim kontak, dan webhook whatsapp.received.
Biaya dan penagihan
WhatsApp dikenakan biaya per pesan, berdasarkan kategori template dan negara penerima; lihat Harga WhatsApp. Pesan ditagih dalam dua langkah, pada dua momen berbeda, dan objek cost pada pesan melaporkan keduanya:
| Field | Keterangan | Kapan tersedia |
|---|---|---|
transaction_amount | Biaya Bird untuk menangani pengiriman | Saat Bird memproses pengiriman yang diterima, sebelum dispatch |
passthrough_amount | Bagian Meta dari harga pesan, yang diteruskan Bird | Saat tanda terima delivered atau read yang berlaku tiba |
amount | Jumlah komponen yang sudah dihargai sejauh ini | Bertambah seiring setiap komponen tersedia |
currency_code | Mata uang dompet organisasi Anda, dipakai oleh kedua komponen | Bersama komponen pertama |
Kedua jumlah berupa string desimal, sebelum pajak.
Catatan: pesan layanan tidak dikenai bagian Meta hingga 30 September 2026, begitu pula template utilitas yang dikirim dalam jendela layanan pelanggan yang terbuka. Mulai 1 Oktober 2026, Meta menagih keduanya, kecuali dalam jendela titik masuk gratis 72 jam, dan dengan 1.000 pesan layanan gratis per nomor telepon bisnis per bulan: Perubahan harga Oktober 2026.
Kedua komponen dihargai berdasarkan input berbeda. Biaya Bird menggunakan kategori template yang Anda kirim dan negara penerima, yang berasal dari kode negara nomor telepon atau, pada pengiriman ke business-scoped user ID, dari prefiks dua huruf pada ID tersebut. Bagian Meta menggunakan kategori yang dilaporkan Meta sendiri pada tanda terima yang berlaku, yang bisa berbeda dari kategori template: Meta dapat melaporkan authentication-international ketika aturan tujuan, lokasi bisnis, dan kelayakannya berlaku. Lihat Tarif authentication-international WhatsApp.
Apa yang dibaca cost bergantung pada seberapa jauh pesan telah berjalan:
- Pada
202,costbernilai null. Belum ada yang dihargai. - Setelah pemrosesan,
transaction_amountterisi danamountsama dengannya.passthrough_amounttetap null. - Setelah tanda terima
deliveredataureadyang berlaku, biaya Meta yang berhasil dicatat mengisipassthrough_amount, danamountmencerminkan komponen yang tercatat.
Komponen null berarti tidak ada jumlah yang tercatat dalam proyeksi tersebut; ini bukan bukti bahwa pesan itu gratis. Komponen yang secara eksplisit dihargai nol terbaca "0.00000".
Kedua tagihan juga gagal secara berbeda. Biaya Bird gagal secara tertutup: ketika tidak dapat diproses setelah 202 karena wallet tidak mencukupi untuk pengiriman atau rute tidak memiliki harga yang dikonfigurasi, pesan berakhir rejected dengan kode kesalahan insufficient_balance atau price_not_found, dan tidak ada yang ditagih. Pesan rejected tidak pernah sampai ke WhatsApp, dan inilah yang membedakannya dari failed. Bagian Meta gagal secara terbuka: jika saldo wallet kurang atau tarif tidak tersedia saat tanda terima tiba, tagihan dilewati tanpa membalikkan status pesan yang teramati. Pengiriman Anda tidak pernah tertahan oleh tagihan kedua.
Pesan yang ditagih oleh Bird mempertahankan tagihan keluar tersebut jika pengiriman kemudian gagal. Biaya Meta diproses dari callback delivered atau read yang berlaku ketika Meta melaporkan harga reguler dengan kategori dan tujuan yang dapat diselesaikan. Kedua jalur callback menggunakan identitas biaya yang sama dan mengandalkan deduplikasi layanan penagihan. Rekonsiliasi tanda terima yang diulang terhadap catatan penagihan, jangan perlakukan proyeksi pesan sebagai bukti debit permanen. Harga service atau free-entry dapat membuat komponen Meta menjadi nol; komponen yang belum diselesaikan bukan bukti bahwa pesan itu gratis.
Gunakan buku besar penagihan untuk rekonsiliasi keuangan. Field cost pada pesan adalah proyeksi tagihan dan bisa tertinggal atau tetap tidak lengkap. Lihat Metrik WhatsApp untuk perbedaan antara observasi pesan dan catatan penagihan.
Event WhatsApp tidak membawa biaya. Untuk membaca salah satu komponen, baca kembali pesan dengan GET /v1/whatsapp/messages/{id}.
Langkah berikutnya
- Pesan layanan: kirim teks, media, dan konten interaktif dalam jendela layanan pelanggan
- Template: pilih template dan isi variabelnya
- Menerima pesan WhatsApp: baca balasan dan ambil media masuk
- Webhook status pesan: terima pembaruan pengiriman dan kegagalan
- Idempotensi: coba lagi dengan aman menggunakan header
Idempotency-Key
Sumber daya terkait
Lanjutkan dengan dokumentasi, panduan, dan contoh untuk topik ini.