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 Email inbound dan HMAC signature

Webhook Email inbound

Halaman ini mencakup dua topik yang berkaitan:

  1. Bagaimana webhook-ingestor menerima email inbound dari SendGrid Inbound Parse atau relay kustom.
  2. Referensi lengkap algoritma HMAC-SHA256 yang dipakai untuk memvalidasi webhook Meta (WhatsApp/Instagram/Messenger) dan email.

Endpoint

ItemNilai
MetodePOST
Path/webhook/email
Hostwebhook-ingestor di WEBHOOK_INGESTOR_PORT (default 3001)
AuthHeader X-Email-Webhook-Signature (opsional — dilewati jika EMAIL_WEBHOOK_SECRET kosong)
Content-Typeapplication/json

Tidak ada handshake verifikasi seperti Meta — SendGrid dan relay lain tidak memakai challenge/subscribe. Ingestor hanya menerima POST.

Format payload

Ingestor menerima JSON SendGrid-compatible. Field minimum yang wajib ada: from. Tanpa from, request ditolak 400 PayloadError.

Code
{ "from": "customer@example.com", "to": "support@yourdomain.com", "subject": "Butuh bantuan invoice #12345", "text": "Halo tim, saya belum menerima invoice untuk order #12345. Mohon dicek.", "html": "<p>Halo tim, saya belum menerima invoice...</p>", "headers": { "Message-ID": "<abc123@mail.example.com>", "In-Reply-To": null, "References": null }, "attachments": [] }

Implementasi ada di crates/webhook-ingestor/src/routes.rs::receive_email_webhook.

Alur pemrosesan

  1. Verifikasi signature (opsional) — jika EMAIL_WEBHOOK_SECRET di .env terisi, ingestor memvalidasi header X-Email-Webhook-Signature dengan HMAC-SHA256. Jika secret kosong, verifikasi dilewati dan semua request diterima.
  2. Parse JSON — body diuraikan menjadi serde_json::Value. Kegagalan parse menghasilkan 400 PayloadError.
  3. Validasi minimum — field from wajib ada; tanpa itu 400.
  4. Bangun event — dikemas sebagai InboundRawEvent dengan channel: "email" dan integration_account_id: None (email tidak memakai lookup akun di ingestor; chat-engine yang menentukan route).
  5. Produksi ke Kafka — topik channel.inbound.raw, key event_id UUID.
  6. Audit — tulis fire-and-forget ke MongoDB webhook_audit dengan source: "email".
Code
let event = InboundRawEvent { event_id, timestamp: now, channel: "email".to_string(), integration_account_id: None, payload: payload.clone(), };

Konsumsi oleh chat-engine-email

chat-engine-email berlangganan channel.inbound.raw dengan filter channel == "email" dan:

  1. Mengekstrak from, to, subject, text/html dari payload.
  2. Mencari integration account berdasarkan alamat to yang cocok dengan config->>'inbox_address'.
  3. Upsert kontak di contacts berdasarkan email pengirim.
  4. Threading conversation berdasarkan header In-Reply-To / References bila ada, jika tidak membuat conversations baru.
  5. Simpan pesan ke MongoDB messages.
  6. Publikasi ConversationUpdateEvent ke Redis.

Parser ada di crates/chat-engine/src/parser/email.rs (fungsi parse_email_message).

HMAC signature untuk Meta dan Email

Bagian ini adalah referensi algoritma yang dipakai oleh kedua verifikasi signature. Kodenya ada di crates/webhook-ingestor/src/signature.rs.

Algoritma

Code
signature = hex( hmac_sha256(secret_key, raw_body_bytes) )
  • Key — untuk Meta: META_APP_SECRET. Untuk email: EMAIL_WEBHOOK_SECRET.
  • Message — raw bytes body sebelum parsing JSON. Jangan memformat ulang atau mem-prettify — satu byte berubah, signature invalid.
  • Digest — SHA-256 (256 bit / 32 byte).
  • Encoding — hex lowercase.

Format header

ChannelHeaderNilai
WhatsAppX-Hub-Signature-256sha256=<hex> (prefix sha256= wajib)
InstagramX-Hub-Signature-256sha256=<hex>
MessengerX-Hub-Signature-256sha256=<hex>
EmailX-Email-Webhook-Signature<hex> (tanpa prefix)

Perbedaan prefix adalah konvensi Meta vs format kustom kami. Fungsi verify_signature di signature.rs menangani prefix strip otomatis untuk header Meta.

Verifikasi yang aman (constant-time)

Jangan membandingkan signature dengan == — vulnerable terhadap timing attack. Gunakan comparator konstan:

Code
use hmac::{Hmac, Mac}; use sha2::Sha256; type HmacSha256 = Hmac<Sha256>; pub fn verify_signature( secret: &str, body: &[u8], signature_header: &str, ) -> Result<(), WebhookError> { let sig_hex = signature_header .strip_prefix("sha256=") .unwrap_or(signature_header); let expected = hex::decode(sig_hex) .map_err(|_| WebhookError::InvalidSignature)?; let mut mac = HmacSha256::new_from_slice(secret.as_bytes()) .map_err(|_| WebhookError::Internal("HMAC init failed".into()))?; mac.update(body); mac.verify_slice(&expected) .map_err(|_| WebhookError::InvalidSignature) }

Hmac::verify_slice adalah fungsi comparator constant-time: waktu eksekusinya tidak bergantung pada byte mana yang pertama berbeda. Ini mencegah attacker mengukur respons untuk mengkonstruksi signature valid.

Men-generate signature (untuk klien)

Jika Anda membangun relay email yang mengirim ke /webhook/email, generate signature dengan Python:

Code
import hmac import hashlib secret = "your-email-webhook-secret".encode() body = b'{"from":"user@example.com","to":"support@yourdomain.com",...}' signature = hmac.new(secret, body, hashlib.sha256).hexdigest() # Kirim sebagai header: X-Email-Webhook-Signature: <signature>

Dengan Node.js:

Code
import crypto from "crypto"; const signature = crypto .createHmac("sha256", process.env.EMAIL_WEBHOOK_SECRET) .update(rawBody) .digest("hex");

Catatan raw body: jika relay Anda mem-parse JSON dan men-serialisasi ulang sebelum mengirim, signature akan invalid. Pastikan Anda meng-HMAC byte yang sama persis dengan yang dikirim.

Troubleshooting

  • 400 Missing required field 'from' — payload email tidak memiliki field from. SendGrid Inbound Parse selalu mengirimnya; periksa apakah relay Anda memakai format lain.
  • 401 InvalidSignature pada webhook email — EMAIL_WEBHOOK_SECRET di relay berbeda dengan yang di ingestor, atau raw body dimutasi oleh proxy/middleware. Gunakan Wireshark atau log akses untuk membandingkan byte.
  • 401 MissingSignature pada webhook Meta tetapi header muncul di proxy — reverse proxy Anda menghapus header X-Hub-Signature-256. Tambahkan:
    Code
    proxy_set_header X-Hub-Signature-256 $http_x_hub_signature_256;
  • Verifikasi Meta lolos tetapi email tidak — ingat prefix header berbeda. Email memakai hex tanpa prefix sha256=. Jika relay Anda menambahkan prefix, ingestor akan menolaknya.
  • Email inbound tidak muncul di inbox — periksa chat-engine-email logs. Biasanya karena alamat to tidak cocok dengan integration_accounts.config->>'inbox_address'.

File terkait

  • HTTP handler: crates/webhook-ingestor/src/routes.rs::receive_email_webhook
  • Signature verification: crates/webhook-ingestor/src/signature.rs::verify_signature, verify_email_signature
  • Parser chat-engine: crates/chat-engine/src/parser/email.rs::parse_email_message
  • Sender: crates/message-sender/src/email.rs (menggunakan SMTP lewat lettre)
Last modified on June 8, 2026
Webhook MessengerOutgoing webhook — dispatcher, event schema, dan retry
On this page
  • Endpoint
  • Format payload
  • Alur pemrosesan
  • Konsumsi oleh chat-engine-email
  • HMAC signature untuk Meta dan Email
    • Algoritma
    • Format header
    • Verifikasi yang aman (constant-time)
    • Men-generate signature (untuk klien)
  • Troubleshooting
  • File terkait
JSON
Rust
Rust
Javascript