Skip to Content

Webhook

The official API is push-based: Meta delivers inbound messages and status updates to a webhook URL you host. wa.webhook() returns a framework-agnostic handler — (req: Request) => Promise<Response> — that does two jobs:

  1. Answers the GET verification challenge using your verifyToken.
  2. Verifies the X-Hub-Signature-256 HMAC on POSTs using your appSecret (rejecting forgeries with 401), then dispatches events into your Client.

Once mounted, inbound traffic flows through the same typed events as everywhere else — text, image, reaction, button-click, and the cloud-only message-status, template-status, flow-response, and order. See Events & Errors.

Mounting the handler

// app/api/whatsapp/route.ts import { wa } from '@/lib/wa' const handler = wa.webhook() export const GET = handler // verification challenge export const POST = handler // inbound messages + statuses

Registering it with Meta

In the Meta dashboard under WhatsApp → Configuration → Webhook, set the Callback URL to your public endpoint, enter the same Verify token you put in cloud.verifyToken, and subscribe to the messages field. Developing locally? Tunnel with ngrok, cloudflared, or localtunnel.

Runnable examples: cloud-express.ts, cloud-hono.ts, cloud-next-route.ts.

Responses at a glance

RequestConditionResponse
GEThub.verify_token matches200 + hub.challenge
GETtoken mismatch403
POSTvalid X-Hub-Signature-256 (or no appSecret set)200, events dispatched
POSTinvalid / missing signature (with appSecret set)401, nothing dispatched
POSTmalformed body400

The handler always returns 200 after a successful signature check even if your event handler throws — Meta retries aggressively on non-200, so Zaileys acks first and surfaces handler errors through the error event instead.

Last updated on