What happens
Meta cannot register the WhatsApp callback URL. Measured against production on 2026-08-05:
GET https://api.elizacloud.ai/api/eliza-app/webhook/whatsapp?hub.mode=subscribe&hub.verify_token=<token>&hub.challenge=PROBE123
→ 401 {"success":false,"code":"WEBHOOK_SIGNATURE_INVALID","error":"Invalid webhook signature"}
Expected is 200 with the body PROBE123.
Why
Meta verifies a callback URL with a GET carrying hub.mode=subscribe, hub.verify_token and hub.challenge, and no signature header — there is no body to sign. That is the documented handshake.
forwardToWebhookGateway runs validateLocalWebhookSignature on every request regardless of method. For a GET, needsBody is false, so the body is "", and verifyWhatsAppSignature returns false the moment x-hub-signature-256 is absent. Meta's challenge is refused before it is read.
The gateway on the other side already implements the handshake correctly — GET /webhook/:project/whatsapp[/:agentId] compares hub.verify_token and echoes hub.challenge — but Meta hits the Worker URL, so it never gets there.
Why it matters beyond WhatsApp
There are two independent WhatsApp failures in production and they have a mandatory order. The access token has been expired since 2026-02-09 (Session has expired on Monday, 09-Feb-26 09:00:00 PST), but regenerating it changes nothing while the URL cannot be registered. This bug is first.
Current state: SELECT count(*) FROM user_identities WHERE whatsapp_id IS NOT NULL → 0. No WhatsApp user has ever existed.
Fix
Skip the body-signature check for exactly that handshake — WhatsApp platform, GET, hub.mode=subscribe, hub.challenge present — and let it proxy to the gateway, which validates the verify token. The HMAC check on an empty body proves nothing; the verify token is the credential Meta's design uses for this step. Every other WhatsApp request, including every POST, keeps the signature requirement.
Related
The same audit found that Twilio and blooio share the symptom (phone_message_log empty since forever) but their configuration lives with other providers and has not been audited. Separate work.
What happens
Meta cannot register the WhatsApp callback URL. Measured against production on 2026-08-05:
Expected is
200with the bodyPROBE123.Why
Meta verifies a callback URL with a
GETcarryinghub.mode=subscribe,hub.verify_tokenandhub.challenge, and no signature header — there is no body to sign. That is the documented handshake.forwardToWebhookGatewayrunsvalidateLocalWebhookSignatureon every request regardless of method. For a GET,needsBodyis false, so the body is"", andverifyWhatsAppSignaturereturnsfalsethe momentx-hub-signature-256is absent. Meta's challenge is refused before it is read.The gateway on the other side already implements the handshake correctly —
GET /webhook/:project/whatsapp[/:agentId]compareshub.verify_tokenand echoeshub.challenge— but Meta hits the Worker URL, so it never gets there.Why it matters beyond WhatsApp
There are two independent WhatsApp failures in production and they have a mandatory order. The access token has been expired since 2026-02-09 (
Session has expired on Monday, 09-Feb-26 09:00:00 PST), but regenerating it changes nothing while the URL cannot be registered. This bug is first.Current state:
SELECT count(*) FROM user_identities WHERE whatsapp_id IS NOT NULL→ 0. No WhatsApp user has ever existed.Fix
Skip the body-signature check for exactly that handshake — WhatsApp platform,
GET,hub.mode=subscribe,hub.challengepresent — and let it proxy to the gateway, which validates the verify token. The HMAC check on an empty body proves nothing; the verify token is the credential Meta's design uses for this step. Every other WhatsApp request, including every POST, keeps the signature requirement.Related
The same audit found that Twilio and blooio share the symptom (
phone_message_logempty since forever) but their configuration lives with other providers and has not been audited. Separate work.