Guide to WhatsApp Webhooks: Features and Best Practices
WhatsApp is the world's most popular messaging platform, with over two billion users across 180+ countries. The WhatsApp Business Platform, powered by Meta's Cloud API, lets businesses send and receive messages at scale. Webhooks are the backbone that makes real-time integrations possible. Whether you're building a customer support chatbot, order notification system, or marketing automation flow, webhooks are how your application finds out that something happened on WhatsApp.
This guide covers the features, behavior, and best practices for working with WhatsApp Cloud API webhooks, along with common limitations developers encounter and practical workarounds.
WhatsApp webhook features
The following table summarizes the key webhook features available on the WhatsApp Business Platform (Cloud API):
| Feature | Details |
|---|---|
| Protocol | HTTPS (valid SSL certificate required) |
| Direction | Provider-to-consumer (push) |
| HTTP method | POST (notifications), GET (verification) |
| Data format | JSON |
| Authentication | Verify Token (setup) + HMAC-SHA256 signature (X-Hub-Signature-256) |
| Webhook fields | messages, account_update, message_template_status_update, phone_number_quality_update, phone_number_name_update, business_capability_update, security, flows, and others |
| Event types | Inbound messages, message status updates (sent, delivered, read, failed), account violations, template status changes, phone number quality changes |
| Supported message types | Text, image, video, audio, document, sticker, location, contacts, interactive (buttons, lists), reactions, flows, product inquiries, ad referrals |
| Retry mechanism | Exponential backoff for up to 7 days |
| Success criteria | HTTP 200 OK response |
| Response timeout | 5–10 seconds (varies by BSP; Meta recommends under 10s) |
| Payload size limit | Up to 3 MB |
| Webhook configuration levels | Phone Number Webhook (primary), WABA Webhook (fallback) |
| Delivery guarantee | At-least-once |
| Ordering guarantee | None (events may arrive out of order) |
| Signature algorithm | HMAC-SHA256 using App Secret |
| Manual retry/replay | Not supported natively |
How WhatsApp webhooks work
WhatsApp webhooks follow a standard push-based pattern. When an event occurs (a customer sends a message, a message you sent is delivered, or a template's status changes) Meta's servers POST a JSON payload to a URL you've registered. Your server processes the payload and returns an HTTP 200 OK to acknowledge receipt.
There are two distinct phases to the webhook lifecycle:
Webhook verification (GET)
When you first register a webhook URL in the Meta App Dashboard, WhatsApp sends a GET request containing three query parameters: hub.mode (always subscribe), hub.verify_token (a string you define during setup), and hub.challenge (a random string). Your endpoint must validate the mode and token, then respond with the challenge value as the response body and an HTTP 200 status code.
Event notifications (POST)
Once verified, WhatsApp delivers event notifications as POST requests. Every notification payload follows the same top-level structure:
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "<WABA_ID>",
"changes": [
{
"value": { ... },
"field": "messages"
}
]
}
]
}
The field property tells you which type of notification you're receiving, and the value object contains the event-specific data.
Webhook subscription fields
WhatsApp organizes its webhook events into subscribable fields. You select which fields you want in the Meta App Dashboard under your app's WhatsApp configuration. The most commonly used fields are:
messages
This is the primary field and the one most developers subscribe to first. It covers both inbound messages and outbound message status updates.
Inbound message notifications include the sender's phone number, the message type (text, image, audio, video, document, sticker, location, contacts, interactive, reaction, order, or system), and the message content or media reference. For media messages, WhatsApp downloads the media first and includes a media ID in the notification that you can use to retrieve the binary content via the API.
Status update notifications report on messages you've sent, with statuses progressing through sent, delivered, read, and potentially failed. Each status update includes the message ID, the recipient's phone number, a timestamp, and — for delivered and read statuses — conversation and pricing metadata.
account_update
Notifies you of account-level events such as policy violations (ACCOUNT_VIOLATION) and restrictions (ACCOUNT_RESTRICTION). Violation notifications include the affected phone number, the event type, and details about the specific policy that was breached. Restriction notifications list all active restrictions and their expiration dates.
message_template_status_update
Template messages are central to WhatsApp's outbound messaging model — you can only initiate conversations using pre-approved templates. This field notifies you when a template's status changes between states like APPROVED, REJECTED, PENDING, DISABLED, PAUSED, or LIMIT_EXCEEDED. Monitoring this field is essential for any application that depends on outbound messaging.
phone_number_quality_update
Fires when a phone number's quality rating changes (between GREEN, YELLOW, and RED) or when quality-related events occur like FLAGGED or UNFLAGGED. Quality ratings directly affect your messaging limits, so this field is critical for operations monitoring.
Other fields
Additional subscribable fields include phone_number_name_update (fires when a phone number's display name is approved or rejected after you request a change), business_capability_update (changes to your messaging tier limits or the maximum number of phone numbers allowed on your WABA), security (security-related alerts for your account), and flows (endpoint availability notifications for WhatsApp Flows integrations).
Webhook security
WhatsApp provides two layers of security for webhooks:
Verify Token
During initial webhook registration, you define a secret string (the Verify Token) that WhatsApp includes in the verification GET request. This confirms your endpoint is expecting WhatsApp's connection — but it's only used once during setup.
Payload signature verification
For every POST notification, Meta signs the payload body using HMAC-SHA256 with your app's App Secret and includes the signature in the X-Hub-Signature-256 header, prefixed with sha256=. To validate:
- Compute the HMAC-SHA256 hash of the raw request body using your App Secret.
- Compare the resulting hash to the value in the
X-Hub-Signature-256header (after stripping thesha256=prefix). - Use a timing-safe comparison function (like
crypto.timingSafeEqualin Node.js orhmac.compare_digestin Python) to avoid timing attacks.
Two critical implementation details: you must verify against the raw request body before any JSON parsing middleware transforms it, and be aware that Meta uses escaped Unicode encoding for special characters when generating the signature.
Retry behavior
When your endpoint returns anything other than an HTTP 200 OK (or fails to respond within the timeout window) WhatsApp considers the delivery failed and queues the notification for retry. Retries follow an exponential backoff schedule and continue for up to 7 days.
This retry window is generous, but it introduces two important considerations. First, if your endpoint is down for an extended period, you'll receive a burst of queued notifications when it comes back online, and your server needs the capacity to absorb that spike. Meta recommends your webhook servers can handle 3x your outgoing message traffic plus 1x your expected incoming message traffic. Second, retries mean your handler will inevitably receive duplicate notifications, making idempotent processing essential (more on that below).
If delivery continues to fail after 7 days, the notification is dropped permanently. There is no dead-letter queue and no way to manually retrieve or replay lost events from Meta's side.
Best practices
Acknowledge first, process later
The single most important best practice for WhatsApp webhooks is to return HTTP 200 immediately upon receiving the request, then process the payload asynchronously. WhatsApp's timeout window is strict (5–10 seconds depending on your BSP), and any processing that takes longer (database writes, API calls, LLM inference) risks triggering retries and duplicate deliveries.
Use a message queue (like Hookdeck, SQS, RabbitMQ, or Redis Streams) to decouple ingestion from processing. Your webhook handler should validate the signature, enqueue the payload, and respond.
Implement idempotent processing
WhatsApp delivers notifications at-least-once, which means duplicates are a normal operating condition, not an edge case. Every webhook handler should be idempotent.
Use the message ID (messages[].id for inbound messages, statuses[].id for status updates) as a deduplication key. Store processed IDs in a fast-lookup store like Redis (with a TTL of a few hours) and skip any payload whose ID you've already seen.
Handle out-of-order events
WhatsApp does not guarantee event ordering. You may receive a read status update before the corresponding delivered update, or process a newer message before an older one. Design your state machines to handle this, for example, if you receive a read status, you can safely infer that delivered also occurred, even if that specific event hasn't arrived yet. Use the timestamp field rather than arrival order to determine the true sequence.
Verify signatures in production
Always validate the X-Hub-Signature-256 header in production. Skipping verification leaves your endpoint open to spoofed payloads. Ensure you're working with the raw body (before JSON parsing middleware processes it) and using a constant-time comparison to prevent timing attacks.
Plan for capacity
For high-volume applications, capacity planning is critical. Meta's throughput starts at 80 messages per second per phone number (upgradable to 1,000 MPS — exceeding this limit triggers error code 130429), and each outbound message can generate up to three webhook callbacks (sent, delivered, read). A business sending 500 messages per second at peak could receive 1,500+ status webhooks per second, plus inbound message traffic. There's also a per-user "pair rate limit" of one message every six seconds. Ensure your infrastructure can absorb this load with headroom for retry bursts.
Monitor template and quality statuses
Subscribe to message_template_status_update and phone_number_quality_update to catch issues before they impact your messaging capability. A template rejection or a phone number flagged for low quality can silently break your outbound flows if you're not watching these signals.
WhatsApp webhook limitations and pain points
Silent webhook failures from missing WABA subscription
The Problem: You configure your webhook URL, toggle the "Subscribed" switch to ON in Meta's developer dashboard, and Meta's test button works perfectly — but when real messages arrive from actual users, nothing reaches your endpoint.
Why It Happens: Meta's developer experience changed in late 2025. In the older UI, creating an app and adding a phone number would automatically register the WABA-to-App subscription. In the newer UI, these settings are more fragmented, and the subscription can silently fail to register. Your webhook URL is verified and configured, but your app is never actually subscribed to receive events from the WABA.
Workarounds:
- Manually register the subscription via the Graph API Explorer by making a POST request to
/{WABA_ID}/subscribed_apps. - Verify the subscription exists by making a GET request to the same endpoint.
- Add a startup check in your deployment pipeline that confirms the subscription is active.
How Hookdeck Can Help: Hookdeck provides a stable, dedicated ingestion URL that you register once with Meta. Because Hookdeck acts as a proxy between WhatsApp and your application, you can verify events are being received at the Hookdeck layer independently of your application's availability. If events stop flowing, the Hookdeck dashboard makes it immediately obvious — reducing the time to detect and diagnose silent subscription failures.
No built-in dead-letter queue or manual replay
The Problem: If your endpoint is down or returns errors for more than 7 days, notifications are permanently dropped. If a bug in your handler causes it to discard or misprocess events before you notice the issue, there's no way to request those events again from Meta.
Why It Happens: Meta's retry system is designed for transient failures, not extended outages. After the 7-day exponential backoff window expires, events are discarded. WhatsApp does not offer an event log, replay API, or dead-letter queue as part of the Cloud API.
Workarounds:
- Build your own persistence layer by logging every raw webhook payload to durable storage (like S3 or a database) before any processing occurs. This gives you a local event store you can replay from.
- Implement health checks and alerting on your webhook endpoint so you're aware of failures within minutes, not days.
How Hookdeck Can Help: Hookdeck automatically persists every event it receives and provides a built-in dead-letter queue for failed deliveries. You can inspect, filter, and replay events from the dashboard (individually or in bulk). If your application was down for hours, you can replay the entire backlog once it recovers, with no data loss.
Duplicate webhook deliveries
The Problem: Your application processes the same message or status update multiple times, leading to duplicate bot responses, duplicate database entries, or duplicate downstream API calls.
Why It Happens: WhatsApp uses at-least-once delivery, so retries during transient network issues will produce duplicates. Additionally, WhatsApp's message lifecycle generates multiple webhook events for a single message (one for each status change), and misconfigured setups (such as multiple Meta apps pointing at the same phone number, or redundant webhook triggers in third-party BSPs) can multiply the problem further.
Workarounds:
- Implement deduplication using message IDs stored in Redis or a database with a short TTL.
- Use a single Meta app per phone number to avoid duplicate subscriptions.
- Ensure your webhook handler is fully idempotent so that processing the same event twice produces the same result.
How Hookdeck Can Help: Hookdeck supports content-based deduplication — you can configure a deduplication key (like the message ID in the payload) and a time window, and Hookdeck will automatically suppress duplicate deliveries to your application. This moves the deduplication logic out of your application code and into the infrastructure layer.
No ordering guarantees
The Problem: Status updates arrive out of sequence. So for example, a read notification arrives before delivered, or a message received later in time is delivered to your webhook before an earlier one. This can corrupt state machines, produce incorrect audit trails, or trigger logic errors in chatbot flows.
Why It Happens: WhatsApp processes and delivers webhooks through distributed infrastructure. Different events for the same message may be routed through different processing paths with varying latencies. Meta does not guarantee ordering, and their documentation explicitly notes that you should use timestamps rather than arrival order.
Workarounds:
- Always use the
timestampfield in each webhook event to determine the true order of events. - Design state transitions to be monotonic (e.g., a message can move from
sent→delivered→readbut never backward). - Buffer events briefly and reorder by timestamp before processing if strict ordering is required.
How Hookdeck Can Help: Hookdeck's event ordering features let you define an ordering key (such as the message ID or conversation ID) to ensure events within the same logical group are delivered to your application in sequence. This is handled at the infrastructure layer, so your application receives events in the correct order without needing to implement reordering logic.
Limited webhook delivery visibility
The Problem: When webhooks fail to reach your endpoint, Meta provides minimal diagnostic information. You know your messages are sending, but you can't see whether status callbacks or inbound message notifications are actually being delivered, what HTTP status codes your endpoint returned, or how many events are queued for retry.
Why It Happens: Meta's developer dashboard is primarily designed for app configuration, not operational monitoring. There is no delivery log, no event timeline, and no way to inspect individual webhook payloads or responses from Meta's side. Debugging typically requires correlating your server-side logs with expected events, which is impossible if the events never reached your server in the first place.
Workarounds:
- Instrument your webhook endpoint with detailed request logging (including headers, body, and response codes).
- Use an external monitoring service to detect endpoint downtime.
- Set up synthetic test messages periodically to verify the full pipeline is working end to end.
How Hookdeck Can Help: Hookdeck's dashboard provides full visibility into every event: the raw request, your endpoint's response, delivery latency, retry attempts, and error details. You can filter, search, and inspect events in real time. When something breaks, you can see exactly which events failed and why, without depending on Meta's tooling.
Local development and testing friction
The Problem: WhatsApp requires a publicly accessible HTTPS URL with a valid SSL certificate to deliver webhooks. Local development environments run on localhost, which means you can't receive real webhook events without additional tooling. This creates a slow and frustrating develop-deploy-test cycle.
Why It Happens: This is inherent to WhatsApp's webhook architecture. Verification and delivery both require a reachable public endpoint, and Meta performs SSL validation. There's no sandbox mode that bypasses this requirement. Even Meta's test button in the dashboard sends real HTTP requests to your registered URL.
Workarounds:
- Use a tunneling tool like ngrok or Cloudflare Tunnel to expose your local server with a public HTTPS URL. Be aware that the URL changes each time you restart the tunnel (unless you pay for a fixed subdomain), requiring you to update the webhook URL in Meta's dashboard each time.
- Alternatively, deploy to a staging environment for webhook testing.
How Hookdeck Can Help: Hookdeck provides a stable public URL for webhook ingestion and supports the Hookdeck CLI, which tunnels events to your local development server. Your registered URL in Meta never changes and Hookdeck handles the routing. You can also pause and resume delivery, inspect events in the dashboard, and replay them against your local server without needing to trigger new events from WhatsApp.
Token expiration and session management
The Problem: Access tokens used for the WhatsApp Cloud API expire, and when they do, your ability to send messages and interact with the API breaks. While this doesn't directly prevent webhook reception (webhooks are delivered regardless of your token status), it disrupts the full message lifecycle, so you receive inbound webhooks but can't respond to them, retrieve media referenced in notifications, or confirm your subscription status.
Why It Happens: Meta's authentication system uses short-lived tokens by default. Permanent tokens require additional setup through a System User in Meta Business Manager. Developers often start with temporary tokens during development and forget to implement proper token rotation before going to production.
Workarounds:
- Create a System User in Meta Business Manager and generate a permanent access token.
- Implement token refresh logic with proper error handling.
- Monitor for 401/403 errors in your API calls and trigger alerts when authentication failures occur.
How Hookdeck Can Help: Because Hookdeck sits between Meta and your application, your webhook reception is decoupled from your API authentication state. Even if your API token expires, webhooks continue to arrive at Hookdeck and are queued for delivery. This gives you a buffer to fix token issues without losing events. You can also use Hookdeck's transformations to add authentication headers to forwarded requests, centralizing credential management.
Summary
WhatsApp's webhook system is capable and well-suited for real-time messaging integrations, but its at-least-once delivery model, lack of built-in observability, and dependency on Meta's evolving developer tooling create real operational challenges. The rich event model (covering inbound messages, delivery statuses, template lifecycle changes, and account health signals) gives developers everything they need to build sophisticated messaging applications, but only if the plumbing underneath is reliable.
For low-volume applications with a single phone number and a reliable endpoint, Meta's built-in retry mechanism combined with proper idempotent processing, signature verification, and asynchronous processing will handle most scenarios. As you scale to higher volumes, multiple phone numbers, or mission-critical messaging flows, the gaps in observability, replay capability, and delivery ordering become harder to work around with application-level code alone.
Hookdeck addresses these gaps at the infrastructure layer by providing durable event storage, dead-letter queues, delivery visibility, content-based deduplication, event ordering, and local development tooling, so your application code can focus on business logic rather than webhook reliability plumbing.