Webhooks are how WhatsApp tells your server what's happening — a message arrived, a template was delivered, a button was tapped. This guide covers everything: webhook verification, event types, parsing payloads, handling failures, and building a production-ready webhook handler in Node.js and Python.
A WhatsApp webhook is an HTTP POST request that Meta sends to your server endpoint whenever an event occurs — incoming message, message delivery status, message read receipt, button click, template approval. You configure the webhook URL in your Meta Business Manager (or WA.Expert handles it automatically). Your server must respond with HTTP 200 within 5 seconds or Meta retries.
| Integration approach | Complexity | Best for |
|---|---|---|
| Webhook verification (GET challenge) | ⭐ Required — one-time setup | Meta verifies your endpoint with a GET challenge. Must respond with hub.challenge. |
| Incoming message parsing (POST) | ⭐⭐ Core — JSON parsing | Parse JSON payload, extract sender phone, message type, and text/buttons. |
| Delivery status tracking | ⭐ Informational | Track sent → delivered → read status for each message. |
| Button/interactive reply handling | ⭐⭐ Medium | Parse quick reply button taps and list selection replies. |
Your webhook must be publicly accessible via HTTPS with a valid SSL certificate. Use ngrok for local development. For production: any cloud server (AWS, GCP, DigitalOcean), Vercel, Railway, or Render. The URL format: https://yourdomain.com/webhook/whatsapp
Meta sends a GET request to verify your endpoint before activating it. Your server must check that hub.verify_token matches your configured token, and respond with the hub.challenge value from the query string. If token doesn't match, return HTTP 403.
All WhatsApp events arrive as POST requests with a JSON body. Parse the body and check entry[0].changes[0].value to determine the event type. Always return HTTP 200 immediately — even if you haven't finished processing. Slow responses cause Meta to retry and create duplicate processing.
Meta signs each webhook payload with your App Secret using HMAC-SHA256. The signature is in the X-Hub-Signature-256 header. Verify this before processing any payload to prevent spoofed requests. Skip only in development.
Return HTTP 200 first, then process the event asynchronously (queue it with Bull/BullMQ, SQS, or a simple database queue). This prevents timeouts and handles the case where your downstream processing takes more than 5 seconds.
Always return HTTP 200 before doing any heavy processing. If your server takes more than 5 seconds to respond, Meta marks the delivery as failed and retries — causing duplicate webhook processing. Use a queue (Redis/Bull, SQS, or even a simple database table) to handle events asynchronously.
message.type === "text". Access message.text.body for the message content. The sender's phone is in message.from — a string like "919876543210" (no + prefix in webhook).
message.type === "interactive" AND message.interactive.type === "button_reply". Access message.interactive.button_reply.id and .title to identify which button was tapped.
message.type === "interactive" AND message.interactive.type === "list_reply". Access message.interactive.list_reply.id and .title for the selected list item.
message.type === "image" / "document" / "audio" / "video". Access message[type].id for the media ID. Fetch the actual media via WhatsApp Media API using the media ID.
message.type === "location". Access message.location.latitude, .longitude, .name, .address for the location data sent by the user.
WA.Expert simplifies the Meta webhook complexity — configure your endpoint in WA.Expert and receive clean, normalised event data for all message types.