The send API returned 200. The customer replied. Nothing appeared in the CRM. After an hour of checking templates and phone numbers, the team found the real problem: its WhatsApp webhook receiver understood Meta's payload, while the provider was posting a different callback schema.
A webhook is the asynchronous half of a WhatsApp integration. The send request tells you whether the platform accepted a request. The webhook later tells you whether the message was sent, delivered, read, failed, or answered. The code is usually small. The contract around it is where production systems break.
Quick answer
A WhatsApp webhook is a public HTTPS endpoint that receives event data from Meta or a WhatsApp Business Solution Provider. Build the endpoint for the exact provider schema, acknowledge valid requests quickly, store the raw event before downstream work, process asynchronously, and make every side effect idempotent.
What Is a WhatsApp Webhook?
A WhatsApp webhook is an HTTP callback for events that happen after, or independently of, an API request. Typical events include outbound message status, inbound customer messages, replies to a specific message, template changes, phone-number changes, and account notifications.
It helps to separate three facts that often get collapsed into one:
- Accepted: the sending API accepted your request and returned an ID.
- Delivered: the channel later reported that the message reached the recipient.
- Converted: your own system recorded the intended business action.
A delivery webhook cannot prove a purchase, completed onboarding, or solved support case. Join the message ID to your own order, campaign, conversation, or user record before changing business state.
According to the EngageLab Message Callback API documentation , checked July 15, 2026, its
WhatsApp callback uses HTTP POST with a JSON body and may return multiple records in one rows array. That detail changes the receiver: code that processes only the first record will
quietly drop events.
Choose the WhatsApp Webhook Contract Before You Code
“WhatsApp webhook” describes the job, not one universal payload. If you connect directly to Meta Cloud API, use Meta's verification, envelope, field subscriptions, and signature rules. If you use a Business Solution Provider, use that provider's callback URL, payload, acknowledgement, authentication, and retry rules.
| Decision point | Meta Cloud API | BSP callback example |
|---|---|---|
| Configuration owner | Meta app and WABA subscription | Provider console and account |
| Envelope | object → entry → changes → value |
Provider-defined, such as total → rows[] |
| Verification | GET challenge with a verify token | Provider-defined; may not use a GET challenge |
| Request authenticity | Validate Meta's documented signature | Use the provider's documented mechanism only |
| Status names | Meta-defined message statuses | May include provider-side planning and validation states |
| Support path | Meta developer tooling | Provider logs, console, and support |
Meta's official
WhatsApp Business Platform Postman collection
shows the whatsapp_business_account envelope and requires subscribing an app to the WABA. The provider schema in this guide uses a batched rows array instead. Both carry
WhatsApp events. Their parsers are not interchangeable.
The most expensive copy-paste bug
A handler can return 200 while reading the wrong path. Retry behavior is contract-specific, and EngageLab's callback reference currently documents no retry mechanism, so the event is simply gone while your logs look healthy. Save a real payload fixture from the provider before writing business logic.
Set Up a WhatsApp Webhook in 7 Steps
-
Step 1. Create one HTTPS receiver per provider and environment. Use separate production and test URLs. A path such as
/webhooks/whatsapp/engagelabmakes ownership visible. - Step 2. Configure the callback URL. In the WhatsApp console, open Configuration Management, then Callback Settings, and register the receiver.
- Step 3. Send a controlled test. Use one known destination and record the outbound message ID plus your own order or user ID in custom arguments.
- Step 4. Capture the raw POST body. Keep a redacted fixture for status, inbound message, and notification events. Do not build from a screenshot alone.
- Step 5. Return the required acknowledgement. The current callback reference requires an HTTP
200response within three seconds. - Step 6. Move processing off the request path. Persist or enqueue the event before calling a CRM, bot, warehouse, or notification service.
- Step 7. Rehearse failure. Test duplicate delivery, malformed JSON, a downstream timeout, a worker restart, and a replay before going live.
If you have not completed the outbound side yet, use the separate WhatsApp API send-message guide . Keep this page focused on what happens after the API accepts a request.
Map WhatsApp Webhook Events to Business Actions
A useful event model has three families. Route them separately even if the provider delivers them to one endpoint.
| Family | Examples | Safe system action | Do not assume |
|---|---|---|---|
| Status | sent , delivered , read , failure, timeout |
Append transport state, update delivery analytics, trigger controlled fallback | Read means purchase or intent |
| Response | New message, reply, media, button, interactive response | Create or update a conversation, route to bot/agent, store message context | Every message belongs to an open workflow |
| Notification | Balance, template, phone-number, or WABA account changes | Alert operations and pause only the affected automation | Every notification should stop all traffic |
Status events need a monotonic state policy
Network delivery can be delayed or duplicated. Do not overwrite a later state with an earlier event just because it arrived later. Store an event history, define valid transitions, and keep failure context. Provider-specific states such as target validation, planning, and delivery timeout are useful for funnel analysis, but they should not leak directly into customer-facing copy.
On EngageLab's callback those provider-side states are fully enumerated. The Message Callback reference defines nine per-recipient statuses, which map cleanly onto a delivery funnel:
| Status | Meaning | Funnel use |
|---|---|---|
plan |
Send scheduled for one recipient; plan_user_total carries the campaign size |
Campaign denominator |
target_valid |
Number passed EngageLab-side and Meta-side validation | List quality rate |
sent |
Message accepted by the Meta WhatsApp service | Submission rate |
delivered |
Meta confirmed delivery to the device | Delivery rate |
read |
Read receipt confirmed | Engagement signal |
target_invalid |
Number rejected by EngageLab-side or Meta-side validation | Stage-1 loss |
sent_failed |
Meta returned an error at submission | Stage-2 loss |
delivered_failed |
Submitted, but Meta reported delivery failure | Stage-3 loss |
delivered_timeout |
No Meta confirmation within 5 minutes | Ambiguity bucket, tracked separately from failure |
Failure statuses also carry attribution. loss_step marks the stage (1 invalid target, 2 send failure, 3 delivery failure) and loss_source names the side that dropped it
(engagelab or meta ), so a falling delivery rate is diagnosable per stage instead of landing in one failure bucket.
Two payload fields are easy to miss and worth wiring on day one. custom_args returns whatever you attached at send time in every status callback, so campaign and user IDs correlate
without a lookup table. The conversation and pricing objects carry Meta's conversation origin and billing category, which lets the same events double as spend attribution.
Inbound messages need conversation context
An inbound text is not just a string. Keep the channel message ID, sender, receiving number, message type, timestamp, referenced message, and business account. That context lets a support agent see what the customer replied to and prevents a bot from answering an old campaign as if it were a new support question.
Notifications belong to operations
A template status change, a lower messaging limit, or an account warning can affect many journeys at once. Route these events to an operations queue with an owner and runbook. The worst response is a generic alert that everyone sees and no one owns.
Build a Provider-Specific Node.js Receiver
The example below is intentionally small. It validates the batch shape, stores each raw row with a deduplication key, acknowledges the callback, and leaves business work to an asynchronous worker.
app.post('/webhooks/whatsapp/provider', async (req, res) => {
const batch = req.body;
if (!batch || !Array.isArray(batch.rows)) {
return res.status(400).end();
}
for (const row of batch.rows) {
const eventKind = row.status ? 'status'
: row.response ? 'response'
: row.notification ? 'notification'
: 'unknown';
const eventKey = [
row.message_id || 'no-message-id',
eventKind,
row.itime || 'no-time',
row.status?.message_status || row.response?.event ||
row.notification?.event || 'unknown'
].join(':');
await eventStore.insertIfAbsent({
eventKey,
provider: 'whatsapp-provider',
receivedAt: new Date(),
payload: row
});
}
res.status(200).end();
});
This is a receiver pattern, not a drop-in application. Add a request-size limit, schema validation, log redaction, retention controls, database timeouts, and the provider's current authenticity mechanism. If database persistence cannot reliably finish inside the acknowledgement window, write to a durable queue or log first.
Use a Receive–Persist–Acknowledge Architecture
1. Receive
Limit size, keep the raw body when required, and identify the provider.
2. Persist
Store the event or put it on a durable queue with a dedupe key.
3. Acknowledge
Return the documented success status before slow downstream work.
4. Process
Normalize, update state, call downstream systems, and retry safely.
Idempotency belongs at the side effect, not only at ingress. If two workers race, a unique database constraint, conditional update, or transactional outbox should still prevent a duplicate refund, duplicate ticket, or double reply. Keep a replay tool that can requeue a stored event without bypassing the same guard.
For monitoring, track callback requests, accepted events, invalid payloads, acknowledgement latency, duplicates, worker lag, processing failures, unknown event types, and the age of the oldest unprocessed event. Alert on the gap between “received” and “processed,” not only on HTTP errors.
Secure the WhatsApp Webhook Without Inventing a Signature
The current provider callback reference says the callback does not contain authentication information and advises against permission verification on the receiving API. That is materially different from a Meta Cloud API integration, where the documented signature should be validated against the raw request body.
For a provider callback without a documented signature, use compensating controls and confirm the latest options with support before launch:
- Use an unguessable provider-specific path and never expose it in client code or public logs.
- Require HTTPS, enforce a small request-size limit, and accept only the documented content type and method.
- Ask whether stable source IP ranges, mTLS, or a newer signed-callback option is available. Do not hard-code an IP list that the provider cannot maintain.
- Validate the full schema, fixed channel fields, timestamps, and identifiers before processing.
- Treat every event as untrusted input. Escape content, scan media through a controlled download path, and redact message bodies from routine logs.
- Rate-limit obvious abuse, but do not make the acknowledgement path so strict that valid provider traffic is dropped.
- Give workers least-privilege credentials. A callback event should not carry authority to perform an irreversible action by itself.
If your policy requires cryptographic source authentication, make that a provider-selection requirement. A secret URL is a compensating control, not a signature.
Troubleshoot WhatsApp Webhook Failures by Symptom
| Symptom | Likely cause | First check |
|---|---|---|
| No callbacks arrive | Wrong environment URL, callback not saved, WABA/app not subscribed | Provider console configuration and a controlled test message |
| Repeated callbacks on providers that retry | Slow response or non-200 acknowledgement | p95 acknowledgement latency and receiver logs |
| Events missing after receiver downtime | No retry in the callback contract; EngageLab currently documents none | Reconcile the window from console message history and statistics |
| HTTP 200 but no CRM update | Wrong payload path, only first batch row processed, worker failure | Stored raw fixture vs parser and queue lag |
| Duplicate tickets or replies | Retry delivery or worker race without idempotency | Unique event key and side-effect guard |
| Read arrives before delivered | Network delay or event ordering across workers | Transition policy and event-time history |
| Inbound media fails | Expired URL, authorization, size, or unsupported type | Provider media-download flow and retention policy |
| Unknown events increase | Provider added a status or notification type | Dead-letter sample and current changelog/docs |
Do not test only from a dashboard “send sample” button. Run at least one real outbound template, one customer reply, one media message if supported, and one controlled downstream failure. The happy path proves the URL exists. Failure tests prove the system can be operated.
When a Managed WhatsApp API Stack Is the Better Fit
A direct Meta integration gives engineering teams control over the native contract. A managed provider can reduce onboarding, sender, template, analytics, and support work, but it also introduces a provider-specific callback surface. Compare the full operating model: regions, throughput, support, data handling, callback security, replay tooling, console visibility, and migration path.
EngageLab WhatsApp Business API is relevant when a team wants WhatsApp sending, templates, status visibility, and callback configuration within one managed platform. Validate the exact callback contract and security controls for your account during a pilot.
If acquisition is the starting point, connect the receiver design to the click-to-WhatsApp ads setup . If cost is the blocker, use the dedicated WhatsApp Business API pricing guide instead of turning a technical webhook page into a pricing explainer.
Pilot the callback before you commit the integration
Bring three real payloads, one failure scenario, and your security requirements. A short technical pilot exposes more than a polished feature matrix.
Start a WhatsApp API pilot Review callback requirementsWhatsApp Webhook FAQ
What is the difference between a WhatsApp API response and a webhook?
The API response is synchronous and usually tells you whether a send request was accepted. A webhook is asynchronous and reports later events such as delivery, read, failure, an inbound message, or an account change. Store both message IDs and custom business IDs so the two sides can be joined.
Does a WhatsApp webhook have to be public?
The provider must be able to reach it over HTTPS, so the endpoint is normally internet-accessible. That does not mean it should be broadly trusted. Use the documented authenticity mechanism, strict request limits, schema validation, least privilege, and provider-specific paths.
Why do I receive duplicate WhatsApp webhook events?
Providers may retry when they do not receive the expected acknowledgement, and network failures can make the sender unsure whether your system processed an event. Workers can also race. Design for at-least-once behavior: persist a stable dedupe key and make each downstream side effect idempotent.
How quickly should a WhatsApp webhook return a response?
Follow the exact provider contract. The callback reference used in this guide requires HTTP 200 within three seconds. Keep acknowledgement fast by persisting or enqueueing the event and performing CRM, bot, analytics, and notification work asynchronously.
Can one endpoint handle Meta and multiple WhatsApp providers?
It can, but a shared public route makes authentication, parsing, and incident ownership harder. Separate provider-specific ingress routes are safer. Normalize events only after each adapter has validated its own contract, then send a common internal event to downstream systems.
A reliable WhatsApp webhook is not the handler that works once. It is the event boundary that still behaves when payloads are batched, requests repeat, workers restart, statuses arrive late, and downstream systems fail. Start with the exact provider contract, acknowledge quickly, preserve the raw event, and keep business actions idempotent.






