Menerima balasan & status pesan lewat webhook
Mengirim saja membuat aplikasi Anda buta: ia tidak tahu saat pelanggan membalas, dan tidak tahu apakah pesan benar-benar sampai. Webhook menutup itu — WACO mengirim (POST) tiap kejadian ke URL Anda begitu terjadi.
Mengaktifkan
Di halaman Developer, isi URL webhook
Anda (wajib HTTPS) dan simpan. WACO menampilkan sebuah secret
(whsec_…) untuk memverifikasi tanda tangan — catat sekali, dipakai selamanya.
Kejadian yang Anda terima
Dua jenis, dibedakan oleh medan event:
Balasan masuk — message.received
{
"event": "message.received",
"waktu": "2026-08-13T05:20:00.000Z",
"dari": "628111222333",
"nama": "Budi",
"nomor_bisnis": "628158825011",
"pesan": { "id": "wamid.ABC", "tipe": "text", "teks": "Halo, mau daftar" }
}
Untuk tombol interaktif, pesan.teks berisi label yang ditekan.
Media — gambar, dokumen, audio, video, stiker
Kalau yang masuk media, pesan.tipe menyebut jenisnya dan detailnya di
pesan.data. Caption (bila ada) ikut muncul di pesan.teks:
{
"event": "message.received",
"waktu": "2026-08-13T05:20:00.000Z",
"dari": "628111222333",
"nama": "Budi",
"pesan": {
"id": "wamid.ABC",
"tipe": "image",
"teks": "ini foto KTP-nya",
"data": {
"id": "1085073820629812",
"mime_type": "image/jpeg",
"sha256": "e5f8…",
"caption": "ini foto KTP-nya"
}
}
}
Medan pesan.data mengikuti jenisnya: image/video →
id, mime_type, sha256, caption?; document menambah
filename; audio menambah voice; sticker
menambah animated.
Mengunduh file media
pesan.data.id adalah ID media, bukan URL — filenya tidak ikut di
webhook. Unduh lewat endpoint ini dengan kunci API Anda; gateway yang mengambilnya dari Meta,
jadi Anda tidak perlu kredensial apa pun selain kunci waco_…:
curl -L https://waco.id/api/v1/media/1085073820629812 \ -H "Authorization: Bearer waco_KUNCI_ANDA" \ -o ktp.jpg
Jawabannya adalah file itu sendiri (Content-Type sesuai mime_type).
Anda hanya bisa mengunduh media yang masuk ke nomor Anda sendiri.
Status pesan — message.status
{
"event": "message.status",
"waktu": "2026-08-13T05:21:00.000Z",
"pesan_id": "wamid.ABC",
"status": "delivered",
"ke": "628111222333"
}
status bernilai sent, delivered, read, atau
failed. pesan_id cocok dengan message_id yang dikembalikan
saat Anda mengirim, jadi Anda bisa memetakannya ke pengiriman Anda.
Pesan dari aplikasi WhatsApp di HP — message.echo (opsional)
Kalau nomor Anda memakai mode berdampingan (masih dipakai di aplikasi WhatsApp Business di HP sekaligus lewat API), staf kadang membalas langsung dari HP. Secara bawaan balasan itu tidak dikirim ke webhook Anda, sehingga di sistem Anda percakapannya terlihat sepihak.
Nyalakan “Ikutkan pesan yang dikirim dari aplikasi WhatsApp di HP” di halaman Developer untuk menerima salinannya:
{
"event": "message.echo",
"waktu": "2026-08-30T04:11:00.000Z",
"ke": "628111222333",
"nomor_bisnis": "628770001111",
"sumber": "aplikasi_whatsapp",
"pesan": {
"id": "wamid.ABC",
"tipe": "text",
"teks": "Baik pak, saya cek dulu ya"
}
}
Bedanya dengan message.received: arahnya keluar, jadi bidangnya
ke (bukan dari) dan tidak ada nama. Untuk media,
pesan.data berisi objek aslinya dan bisa diunduh lewat
GET /api/v1/media/{id} seperti pesan masuk. pesan.id memakai wamid
yang sama dengan message.status, jadi keduanya bisa dicocokkan.
Catatan: pesan yang Anda kirim sendiri lewat API tidak ikut di sini —
Anda sudah punya message_id-nya dari respons /kirim. Fitur ini mati
secara bawaan supaya integrasi yang sudah jalan tidak tiba-tiba menerima jenis event baru.
Verifikasi tanda tangan — WAJIB
Setiap POST membawa header X-WACO-Signature: sha256=…, yaitu HMAC-SHA256 atas
body mentah memakai secret Anda. Hitung ulang dan bandingkan sebelum
mempercayai payload — kalau tidak, siapa pun yang tahu URL Anda bisa memalsukannya.
Node.js (pakai body mentah, bukan JSON yang sudah di-parse):
const crypto = require('crypto');
app.post('/webhook/waco', express.raw({ type: 'application/json' }), (req, res) => {
const diterima = req.header('X-WACO-Signature') || '';
const dihitung = 'sha256=' +
crypto.createHmac('sha256', process.env.WACO_WEBHOOK_SECRET)
.update(req.body).digest('hex');
const ok = diterima.length === dihitung.length &&
crypto.timingSafeEqual(Buffer.from(diterima), Buffer.from(dihitung));
if (!ok) return res.sendStatus(401);
const event = JSON.parse(req.body.toString());
// ... proses event ...
res.sendStatus(200);
});
PHP:
$body = file_get_contents('php://input');
$diterima = $_SERVER['HTTP_X_WACO_SIGNATURE'] ?? '';
$dihitung = 'sha256=' . hash_hmac('sha256', $body, getenv('WACO_WEBHOOK_SECRET'));
if (!hash_equals($dihitung, $diterima)) { http_response_code(401); exit; }
$event = json_decode($body, true);
// ... proses event ...
http_response_code(200);
Yang perlu diketahui
- Balas cepat dengan 2xx. Status selain 2xx dianggap gagal, dan WACO mencoba lagi dengan jeda menaik (1 dtk → 5 dtk → 30 dtk → 2 mnt → … sampai berjam-jam). Proses berat sebaiknya diantrekan, bukan dikerjakan sebelum membalas.
- Bisa terkirim lebih dari sekali. Karena ada percobaan ulang, buat handler
Anda idempoten — gunakan
pesan.id/pesan_idsebagai kunci. - Yang dikirim:
message.received,message.status, dan — bila Anda nyalakan —message.echo. Kejadian internal (kualitas nomor, akun) tidak diteruskan ke aplikasi.
Memantau pengiriman
Riwayat pengiriman webhook tampil di halaman Developer (15 event terakhir). Arti statusnya:
- Terkirim — server Anda membalas 2xx; event sudah di tangan Anda.
- Antre — menunggu giliran kirim (biasanya hitungan detik).
- Gagal, dicoba lagi — server Anda tidak membalas 2xx; WACO mengulang dengan jeda menaik sampai ±26 jam.
- Dilewati — tipe event yang memang tidak diteruskan ke aplikasi (mis. salinan pesan dari HP untuk nomor koeksistensi) — bukan kegagalan.
- Berhenti dicoba — gagal terus melewati batas percobaan. Periksa server Anda, lalu hubungi kami bila butuh pengiriman ulang.
/api/v1/balas) selama masih dalam jendela 24 jam — tanpa template.