Receive Meta / WhatsApp webhooks
A complete inbound walkthrough: take Meta Graph webhooks (WhatsApp, Messenger, Instagram), verify the X-Hub-Signature-256 HMAC, and fan them out to your API and a queue — while Emithook auto-answers Meta's GET hub.challenge verification.
What you'll build
Meta ──▶ <ingest-domain>/meta-app/events ──▶ ├─ HTTPS https://api.acme.in/whatsapp
(acked + buffered <100ms; signature verified in processing) └─ SQS acme-whatsapp-q1. Create the endpoint
Use the Meta / WhatsApp preset — it configures the X-Hub-Signature-256 HMAC check and auto-answers the GET hub.challenge verification Meta runs when you save the Callback URL.
curl -X POST https://api.emithook.com/v1/endpoints \
-H "Authorization: Bearer $EK_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://wh.emhk.in/acme/meta-app/events",
"preset": "meta",
"destinations": ["dst_acme_https", "dst_acme_sqs"]
}'// → 201 Created
{
"id": "ep_01JX9...",
"url": "https://<ingest-domain>/meta-app/events",
"verification": "meta",
"status": "active"
}TIP
The URL is live immediately on the shared ingest domain (<ingest-domain>, config-driven) — no DNS. Paste it as the Callback URL in Meta App Dashboard → Webhooks → Edit subscription.
2. Add the app secret
Copy your App Secret from Meta App Dashboard → App Settings → Basic so Emithook can verify the X-Hub-Signature-256 header:
curl -X PUT https://api.emithook.com/v1/endpoints/ep_01JX9.../secret \
-H "Authorization: Bearer $EK_KEY" \
-d '{ "secret": "your-meta-app-secret" }'Meta signs the raw body with HMAC-SHA256 (hex) and sends it as X-Hub-Signature-256: sha256=<hex>. Emithook recomputes it over the raw body and compares in constant time. The edge accepts and durably buffers every request in <100 ms (never dropped); verification then runs in the processing plane. A request that fails verification is quarantined — durable and inspectable, but never delivered (not a 401 at the edge, never silently dropped).
The hub.challenge verification handshake
When you save the Callback URL, Meta sends a GET with hub.mode=subscribe, a hub.verify_token you chose, and a hub.challenge value, and expects the hub.challenge echoed back synchronously. The Meta preset answers this at the edge for you — you never write handshake code, and the subscription verifies on the first try.
3. What an incoming request looks like
POST /meta-app/events HTTP/1.1
Host: <ingest-domain>
X-Hub-Signature-256: sha256=b4d3f1...
Content-Type: application/json
{ "object": "whatsapp_business_account", "entry": [ { "changes": [ { "field": "messages" } ] } ] }HTTP/1.1 200 OK
{ "received": true }Emithook acks in under 100 ms, then fans the event out to both destinations independently — each signed, retried, and logged on its own.
4. Confirm delivery
emithook logs tail --endpoint /meta-app/events12:04:31 evt_01JX… messages → dst_acme_https 200 142ms ✓
12:04:31 evt_01JX… messages → dst_acme_sqs enqueued ✓If api.acme.in is down, Emithook retries with backoff and parks events behind the circuit breaker — when it recovers, they drain automatically. Nothing is lost.
5. Replay if needed
# replay everything that dead-lettered for this endpoint
emithook replay --dlq --endpoint /meta-app/events