Platform
Chatbot Builder Bulk Messaging Team Inbox Mini CRM API & Webhooks AI Integration WhatsApp Flows
Industries
E-commerce & D2C Real Estate Education Healthcare Finance & BFSI Logistics Hospitality Retail
Integrations 📚 Learn 🗂 Codex Compare Pricing About Contact Start Free Trial →
Technical Guide Step-by-Step ⏱ 15 min read

WhatsApp Business API Webhook Guide — Complete Technical Reference

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.

Get WA.Expert API Key → Talk to a Developer

What this guide covers

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 approachComplexityBest for
Webhook verification (GET challenge)⭐ Required — one-time setupMeta verifies your endpoint with a GET challenge. Must respond with hub.challenge.
Incoming message parsing (POST)⭐⭐ Core — JSON parsingParse JSON payload, extract sender phone, message type, and text/buttons.
Delivery status tracking⭐ InformationalTrack sent → delivered → read status for each message.
Button/interactive reply handling⭐⭐ MediumParse quick reply button taps and list selection replies.

How to connect — all methods explained

Method 1 Complete Webhook Setup — Node.js

1

Create HTTPS endpoint

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

2

Handle GET verification request

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.

3

Handle POST webhook events

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.

4

Verify webhook signature

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.

5

Process asynchronously

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.

// Node.js Express — complete webhook handler const express = require('express'); const crypto = require('crypto'); const app = express(); app.use(express.json()); // Step 1: GET — verification challenge app.get('/webhook', (req, res) => { const mode = req.query['hub.mode']; const token = req.query['hub.verify_token']; const challenge = req.query['hub.challenge']; if (mode === 'subscribe' && token === process.env.VERIFY_TOKEN) return res.status(200).send(challenge); res.sendStatus(403); }); // Step 2: POST — incoming events app.post('/webhook', (req, res) => { // Verify signature const sig = req.headers['x-hub-signature-256']; const expected = 'sha256=' + crypto .createHmac('sha256', process.env.APP_SECRET) .update(JSON.stringify(req.body)).digest('hex'); if (sig !== expected) return res.sendStatus(403); res.sendStatus(200); // Always respond first processWebhook(req.body); // Then process async }); // Step 3: Parse the event function processWebhook(body) { const changes = body?.entry?.[0]?.changes; if (!changes) return; for (const change of changes) { const val = change.value; // Incoming message if (val.messages) handleMessage(val.messages[0], val.contacts[0]); // Status update (sent/delivered/read) if (val.statuses) handleStatus(val.statuses[0]); } }

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.

Method 2 Message Types — Parsing All Formats

1

Text messages

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

2

Button replies (quick replies)

message.type === "interactive" AND message.interactive.type === "button_reply". Access message.interactive.button_reply.id and .title to identify which button was tapped.

3

List replies

message.type === "interactive" AND message.interactive.type === "list_reply". Access message.interactive.list_reply.id and .title for the selected list item.

4

Media messages (image, document, audio)

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.

5

Location messages

message.type === "location". Access message.location.latitude, .longitude, .name, .address for the location data sent by the user.

// Parse all WhatsApp message types function handleMessage(msg, contact) { const phone = msg.from; // "919876543210" const name = contact?.profile?.name; switch(msg.type) { case 'text': const text = msg.text.body; // Handle text commands if (text.toUpperCase() === 'STOP') unsubscribe(phone); break; case 'interactive': if (msg.interactive.type === 'button_reply') { const btnId = msg.interactive.button_reply.id; handleButtonReply(phone, btnId); } break; case 'image': const mediaId = msg.image.id; downloadMedia(mediaId); // fetch via API break; case 'location': const {latitude, longitude} = msg.location; break; } }

Common questions

What is the WhatsApp webhook verify token?
The verify token is a secret string you choose and configure in two places: (1) in your Meta Business Manager webhook settings, and (2) in your server code (as an environment variable). When Meta sends the GET verification request, it includes your verify token — your server checks it matches before responding with the challenge. It prevents random parties from verifying a webhook endpoint they don't control.
How do I test webhooks locally during development?
Use ngrok (ngrok.com) — it creates a public HTTPS URL that tunnels to your local server. Command: ngrok http 3000 (replace 3000 with your local port). Use the generated https://xxxx.ngrok.io/webhook URL in Meta's webhook settings. Free ngrok tier works for development — the URL changes each restart, so update Meta settings when you restart ngrok.
What happens if my webhook server is down and Meta can't deliver?
Meta retries failed webhook deliveries for up to 7 days with exponential backoff. If your server is down for a short time, you'll receive all missed webhooks when it comes back online. For messages delivered after a long downtime, check the message timestamp to determine if they're still relevant before processing.
How do I avoid processing duplicate webhook events?
Meta may send the same webhook more than once (duplicate delivery). Each webhook event has a unique message.id. Store processed message IDs in a database or Redis set (with 24-hour TTL). Before processing any event, check if the ID has been seen before. Skip if duplicate, process and store ID if new.
Can I use WA.Expert's webhook instead of building my own?
Yes — WA.Expert handles the WhatsApp webhook entirely on the platform side. You configure a webhook URL in WA.Expert (Settings → Webhooks) and WA.Expert forwards incoming messages to your URL in a normalised, simplified format — stripping out the complex nested Meta webhook structure. For basic use cases, WA.Expert's webhook forwarding is much simpler than building a Meta webhook handler from scratch.
What is the difference between message status webhooks and incoming message webhooks?
Two types: (1) Incoming message — fires when a customer sends you a WhatsApp message. Contains message content, sender phone, and media. (2) Status update — fires when a message you sent changes status: sent (accepted by WhatsApp), delivered (reached recipient's device), read (recipient opened it). Status updates reference the original message ID. You subscribe to both in Meta Business Manager webhook settings.

More connection guides

Ready to build your WhatsApp webhook handler?

WA.Expert simplifies the Meta webhook complexity — configure your endpoint in WA.Expert and receive clean, normalised event data for all message types.

Start Free Trial → Talk to a Developer