OmniStream Docs
  • Panduan Pengguna
  • Developer
  • API Reference
Developer Hub
Pendahuluan
Autentikasi
Model Data
Webhook
    Webhook WhatsApp inboundWebhook Instagram inboundWebhook MessengerWebhook Email inbound dan HMAC signatureOutgoing webhook — dispatcher, event schema, dan retry
WebSocket
Self-Hosting
Error & Rate Limit
Webhook

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

ItemNilai
MetodeGET (verifikasi) dan POST (webhook event)
Path/webhook/whatsapp
Hostwebhook-ingestor di WEBHOOK_INGESTOR_PORT (default 3001)
AuthHMAC-SHA256 header X-Hub-Signature-256

Catatan topologi: webhook-ingestor adalah satu binary yang mengekspos rute WhatsApp, Instagram, Messenger, dan Email bersamaan pada port yang sama. Konfigurasi WEBHOOK_INGESTOR_PORT di .env.example adalah 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:

  1. Memastikan hub.mode == "subscribe".
  2. Membandingkan hub.verify_token dengan META_VERIFY_TOKEN dari .env (nilai hot-reload dari tabel integrations jika ada).
  3. Mengembalikan nilai hub.challenge apa 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
{ "object": "whatsapp_business_account", "entry": [ { "id": "1234567890", "changes": [ { "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "628123456789", "phone_number_id": "987654321" }, "contacts": [{ "profile": { "name": "Budi" }, "wa_id": "628111222333" }], "messages": [ { "from": "628111222333", "id": "wamid.HBgLM...", "timestamp": "1776113600", "type": "text", "text": { "body": "Halo, butuh bantuan" } } ] }, "field": "messages" } ] } ] }

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
SELECT id FROM integration_accounts WHERE channel = 'whatsapp' AND config->>'phone_number_id' = $1 AND is_active = true

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
InboundRawEvent { event_id: Uuid::new_v4(), timestamp: Utc::now(), channel: "whatsapp".to_string(), integration_account_id: account_id, payload: mini_payload, // JSON dari satu entry + satu change }

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
WebhookAudit { event_id, source: format!("whatsapp:{}", phone_number_id), received_at: now, payload: mini_payload, }

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:

  1. Mengekstrak pesan WhatsApp dari payload.entry[0].changes[0].value.messages[0].
  2. Upsert kontak ke tabel contacts berdasarkan nomor telepon.
  3. Upsert conversations untuk (contact_id, integration_account_id).
  4. Simpan pesan lengkap ke koleksi MongoDB messages.
  5. Publikasi ConversationUpdateEvent ke Redis channel conversation_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 mengirim X-Hub-Signature-256. Umumnya disebabkan oleh reverse proxy yang menghapus header. Pastikan nginx/Caddy meneruskan header X-Hub-Signature-256.
  • 401 InvalidSignature — META_APP_SECRET tidak cocok dengan app secret di Meta Dashboard. META_APP_SECRET tidak hot-reload dari DB; restart webhook-ingestor setelah memperbarui .env.
  • 500 setelah signature valid — Kafka broker tidak reachable atau topik channel.inbound.raw belum dibuat. Redpanda membuat topik on-demand jika auto_create_topics_enabled = true.
  • Event tidak muncul di inbox — periksa log chat-engine: pesan mungkin gagal upsert ke contacts jika phone_number_id tidak cocok dengan baris integration_accounts. Cek SELECT * FROM integration_accounts WHERE channel = 'whatsapp'.
  • Duplikasi event — Meta mengirim ulang webhook sampai menerima 200 OK. Idempotency ditangani di chat-engine via event_id dan 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
Last modified on June 8, 2026
Roles & PermissionsWebhook Instagram inbound
On this page
  • Endpoint
  • 1. Handshake verifikasi (GET /webhook/whatsapp)
  • 2. Penerimaan event (POST /webhook/whatsapp)
    • 2.1 Verifikasi HMAC
    • 2.2 Iterasi entry[].changes[]
    • 2.3 Resolusi integration_account_id
    • 2.4 Produksi ke Kafka
    • 2.5 Audit ke MongoDB
  • 3. Konsumsi oleh chat-engine
  • 4. Dari Redis ke WebSocket
  • 5. Dispatch ke webhook keluar
  • Troubleshooting
  • File terkait
JSON
Rust
Rust