Skip to content

eliza-app webhook: Meta's WhatsApp verification GET is rejected, so the callback URL can never be registered #17823

Description

@standujar

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 NULL0. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions