Webhook Instagram inbound
Webhook Instagram inbound
Halaman ini menjelaskan bagaimana webhook-ingestor menangani webhook dari Instagram Graph API. Kode sumbernya ada di crates/webhook-ingestor/src/routes.rs::receive_instagram_webhook.
Integrasi Instagram memakai Instagram Login (OAuth per-akun), berbeda dengan WhatsApp yang memakai kredensial bersama dari Business Account. Akibatnya lookup integration_account_id di Instagram menggunakan entry[].id (Instagram user id) sebagai kunci, bukan phone_number_id.
Endpoint
| Item | Nilai |
|---|---|
| Metode | GET (verifikasi) dan POST (webhook event) |
| Path | /webhook/instagram |
| 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 pada port yang sama. Lihat Webhook WhatsApp inbound untuk detail.
1. Handshake verifikasi (GET /webhook/instagram)
Alur handshake identik dengan WhatsApp: Meta mengirim GET /webhook/instagram?hub.mode=subscribe&hub.verify_token=<token>&hub.challenge=<nonce>, ingestor memeriksa hub.mode + hub.verify_token melawan META_VERIFY_TOKEN, lalu mengembalikan hub.challenge sebagai body plain text.
Token yang sama (META_VERIFY_TOKEN) dipakai untuk ketiga saluran Meta (WhatsApp, Instagram, Messenger) karena Meta tidak membedakannya per-channel.
Kode: routes.rs::verify_instagram_webhook.
2. Penerimaan event (POST /webhook/instagram)
2.1 Verifikasi HMAC
Seperti WhatsApp, Instagram mengirim X-Hub-Signature-256: sha256=<hex>. Ingestor memverifikasi dengan META_APP_SECRET. Meta memakai app secret yang sama untuk semua saluran karena app tunggal di App Dashboard mencakup keduanya.
Detail lengkap verifikasi ada di HMAC signature.
2.2 Lookup integration_account_id
Payload Instagram memiliki bentuk:
Code
entry[].id adalah Instagram user id (akun bisnis yang menerima pesan). Ingestor menggunakannya untuk mencari baris integration account:
Code
Jika tidak ditemukan, integration_account_id diset None. Event tetap diproduksi supaya tidak ada kehilangan data, tetapi chat-engine biasanya menjatuhkannya karena tidak tahu kontak mana yang dimaksud.
2.3 Model token per-akun
Berbeda dengan WhatsApp yang memakai satu META_ACCESS_TOKEN untuk semua nomor, Instagram menyimpan access token terpisah per akun di integration_accounts.config->>'access_token'. Token ini diperoleh ketika admin menyelesaikan alur OAuth Instagram Login dan berlaku selama 60 hari (long-lived token). message-sender-instagram membaca token dari baris yang cocok ketika mengirim balasan.
Detail alur OAuth ada di docs-site/pages/developer/webhook dan implementasi di crates/api-gateway/src/routes/integrations/instagram.rs.
2.4 Produksi ke Kafka dan audit
Sama seperti WhatsApp: event dikemas ke InboundRawEvent dengan channel: "instagram" dan diproduksi ke channel.inbound.raw. Audit ditulis fire-and-forget ke koleksi MongoDB webhook_audit dengan source: "instagram:<ig_user_id>".
Code
3. Konsumsi oleh chat-engine
Binary chat-engine-instagram berlangganan channel.inbound.raw dengan filter channel == "instagram" dan melakukan:
- Mengekstrak pesan dari
payload.entry[0].messaging[0]. - Upsert kontak di
contactsberdasarkansender.id(Instagram Scoped User ID / IGSID). - Upsert
conversationsuntuk(contact_id, integration_account_id). - Simpan pesan ke MongoDB
messages. - Publikasi
ConversationUpdateEventke Redisconversation_updates.
Parser ada di crates/chat-engine/src/parser/instagram.rs (fungsi parse_instagram_message).
4. Dari Redis ke WebSocket
Identik dengan WhatsApp — lihat Webhook WhatsApp inbound.
5. Dispatch ke webhook keluar
Identik dengan WhatsApp. Event untuk pesan inbound Instagram menghasilkan webhook message.received dengan channel: "instagram". Lihat Outgoing webhook.
Perbedaan kunci vs WhatsApp
| Aspek | ||
|---|---|---|
| Kunci lookup | phone_number_id | entry[].id (ig user id) |
| Model token | Shared META_ACCESS_TOKEN | Per-akun, OAuth Instagram Login |
| Lifetime token | Permanen (system user) | Long-lived 60 hari, refresh sebelum expire |
| Format sender | from: "62812..." (MSISDN) | sender.id (IGSID, opaque) |
| Template pre-approved | Wajib untuk outbound di luar 24 jam | Tidak berlaku — Instagram memakai 24h messaging window saja |
Troubleshooting
401 InvalidSignature—META_APP_SECRETtidak cocok. Sama dengan WhatsApp; restart ingestor setelah mengubah.env.- Event tiba tetapi
integration_account_idselaluNone— barisintegration_accountsdenganchannel = 'instagram'tidak memilikiconfig->>'user_id'yang cocok denganentry[].id. Periksa dengan:Code message-sender-instagramgagal401saat reply — access token akun kedaluwarsa. Jalankan refresh token viaPOST /api/integrations/instagram/refresh/:idatau biarkan scheduler menjalankannya otomatis.- Pesan masuk tetapi tidak muncul di inbox —
chat-engine-instagrammungkin mati. Periksa logs; ia adalah binary terpisah darichat-engine-whatsappmeskipun kodenya berbagi.
File terkait
- HTTP handler:
crates/webhook-ingestor/src/routes.rs::receive_instagram_webhook - Verifikasi signature:
crates/webhook-ingestor/src/signature.rs - OAuth handler:
crates/api-gateway/src/routes/integrations/instagram.rs - Parser chat-engine:
crates/chat-engine/src/parser/instagram.rs - Sender:
crates/message-sender/src/instagram.rs