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:
- Answers the GET verification challenge using your
verifyToken. - Verifies the
X-Hub-Signature-256HMAC on POSTs using yourappSecret(rejecting forgeries with401), then dispatches events into yourClient.
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
Next.js
// 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 + statusesRegistering 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
| Request | Condition | Response |
|---|---|---|
GET | hub.verify_token matches | 200 + hub.challenge |
GET | token mismatch | 403 |
POST | valid X-Hub-Signature-256 (or no appSecret set) | 200, events dispatched |
POST | invalid / missing signature (with appSecret set) | 401, nothing dispatched |
POST | malformed body | 400 |
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.