Webhook WhatsApp inbound
Webhook WhatsApp inbound
Halaman ini menjelaskan persis bagaimana webhook-ingestor menangani event webhook WhatsApp dari Meta Cloud API, dari handshake verifikasi awal hingga pesan muncul di inbox melalui WebSocket. Detail diambil langsung dari crates/webhook-ingestor/src/routes.rs.
Endpoint
| Item | Nilai |
|---|---|
| Metode | GET (verifikasi) dan POST (webhook event) |
| Path | /webhook/whatsapp |
| Host | webhook-ingestor di WEBHOOK_INGESTOR_PORT (default 3001) |
| Auth | HMAC-SHA256 header X-Hub-Signature-256 |
Catatan topologi:
webhook-ingestoradalah satu binary yang mengekspos rute WhatsApp, Instagram, Messenger, dan Email bersamaan pada port yang sama. KonfigurasiWEBHOOK_INGESTOR_PORTdi.env.exampleadalah port tunggal untuk semua saluran. Referensi ke "port 3003" untuk Instagram dan "port 3004" untuk Email di beberapa halaman panduan adalah penyederhanaan dokumentasi — secara teknis keempat rute berbagi port yang sama.
1. Handshake verifikasi (GET /webhook/whatsapp)
Saat Anda mendaftarkan callback URL di Meta App Dashboard, Meta mengirim GET /webhook/whatsapp?hub.mode=subscribe&hub.verify_token=<token>&hub.challenge=<nonce>. Ingestor:
- Memastikan
hub.mode == "subscribe". - Membandingkan
hub.verify_tokendenganMETA_VERIFY_TOKENdari.env(nilai hot-reload dari tabelintegrationsjika ada). - Mengembalikan nilai
hub.challengeapa adanya sebagai body plain text.
Jika token tidak cocok, ingestor mengembalikan 401 Unauthorized dan menulis ERROR WhatsApp webhook verify token mismatch ke log.
Kode: routes.rs::verify_whatsapp_webhook.
2. Penerimaan event (POST /webhook/whatsapp)
Saat pesan masuk, Meta mengirim POST /webhook/whatsapp dengan body JSON dan header X-Hub-Signature-256: sha256=<hex>. Alurnya:
2.1 Verifikasi HMAC
Ingestor membaca X-Hub-Signature-256, menerapkan hmac-sha256 dengan kunci META_APP_SECRET, dan membandingkan dengan cara constant-time (Hmac::verify_slice). Header yang hilang atau signature yang tidak cocok menghasilkan 401 Unauthorized dengan body InvalidSignature.
Detail algoritma dan contoh kode ada di HMAC signature.
2.2 Iterasi entry[].changes[]
WhatsApp Cloud API membungkus satu atau banyak perubahan dalam satu webhook request. Contoh singkat:
Code
Ingestor tidak mengasumsikan hanya ada satu perubahan. Ia melakukan iterasi entry[].changes[] dan mengemit satu event Kafka terpisah per-perubahan. Ini disengaja: Meta bisa membatch perubahan untuk beberapa nomor WhatsApp milik tenant berbeda dalam satu request, dan menyatukan semuanya ke satu integration_account_id akan menyebabkan kebocoran lintas-tenant.
2.3 Resolusi integration_account_id
Untuk setiap perubahan, ingestor mengambil value.metadata.phone_number_id dan mencari baris di integration_accounts:
Code
Jika tidak ditemukan (mis. akun belum terhubung), integration_account_id diset None — event tetap diproses, tetapi chat-engine mungkin menjatuhkannya.
2.4 Produksi ke Kafka
Tiap event dikemas ke struct InboundRawEvent (omni-common::models::events) dan diproduksi ke topik Kafka channel.inbound.raw (satu topik untuk semua saluran, bukan per-saluran):
Code
Key record adalah event_id (UUID). Jika produksi Kafka gagal dalam 5 detik, ingestor merespons 500 Internal Server Error dan Meta akan mengirim ulang dengan backoff.
2.5 Audit ke MongoDB
Paralel dengan produksi Kafka, ingestor mengirimkan WebhookAudit ke koleksi MongoDB webhook_audit dengan format:
Code
Audit ditulis secara fire-and-forget (tokio::spawn) supaya latensi respons tidak terpengaruh jika Mongo lambat. Kegagalan ditulis ke log tetapi tidak menggagalkan request.
3. Konsumsi oleh chat-engine
Binary chat-engine-whatsapp (sebenarnya berbagi kode dengan saluran lain via flag) berlangganan channel.inbound.raw, mengurai InboundRawEvent, dan:
- Mengekstrak pesan WhatsApp dari
payload.entry[0].changes[0].value.messages[0]. - Upsert kontak ke tabel
contactsberdasarkan nomor telepon. - Upsert
conversationsuntuk(contact_id, integration_account_id). - Simpan pesan lengkap ke koleksi MongoDB
messages. - Publikasi
ConversationUpdateEventke Redis channelconversation_updates.
Parser pesan ada di crates/chat-engine/src/parser/whatsapp.rs.
4. Dari Redis ke WebSocket
ws-server berlangganan conversation_updates. Event yang masuk dirutekan:
- Admin/supervisor: broadcast ke semua.
- Agent: hanya dikirim ke
assigned_agent_id. Percakapan baru yang belum ter-assign dikirim ke semua supaya agent bisa mengklaimnya.
Lihat Koneksi WebSocket dan Events untuk detailnya.
5. Dispatch ke webhook keluar
Paralel dengan WebSocket, webhook_dispatcher di api-gateway juga berlangganan conversation_updates dan menerjemahkan event menjadi tipe webhook keluar. Event untuk pesan inbound dipetakan menjadi message.received. Detail lengkap ada di Outgoing webhook.
Troubleshooting
401 Missing signature header— Meta tidak mengirimX-Hub-Signature-256. Umumnya disebabkan oleh reverse proxy yang menghapus header. Pastikan nginx/Caddy meneruskan headerX-Hub-Signature-256.401 InvalidSignature—META_APP_SECRETtidak cocok dengan app secret di Meta Dashboard.META_APP_SECRETtidak hot-reload dari DB; restartwebhook-ingestorsetelah memperbarui.env.500setelah signature valid — Kafka broker tidak reachable atau topikchannel.inbound.rawbelum dibuat. Redpanda membuat topik on-demand jikaauto_create_topics_enabled = true.- Event tidak muncul di inbox — periksa log
chat-engine: pesan mungkin gagal upsert kecontactsjikaphone_number_idtidak cocok dengan barisintegration_accounts. CekSELECT * FROM integration_accounts WHERE channel = 'whatsapp'. - Duplikasi event — Meta mengirim ulang webhook sampai menerima
200 OK. Idempotency ditangani dichat-engineviaevent_iddan timestamp pesan.
File terkait
- HTTP handler:
crates/webhook-ingestor/src/routes.rs::receive_whatsapp_webhook - Verifikasi signature:
crates/webhook-ingestor/src/signature.rs - Router utama:
crates/webhook-ingestor/src/main.rs - Kafka produser: sama file,
emit_whatsapp_event - Parser chat-engine:
crates/chat-engine/src/parser/whatsapp.rs