The role of the webhook in a WhatsApp integration
Sending a message is the easy part. The hard part is knowing what happened next: did the customer reply? Did they read it? Did the number disconnect in the middle of the night? In a WhatsApp API, the webhook answers those questions. On every event, the API sends a POST to a URL you control, with a JSON body describing what happened.
This flips the integration logic. Instead of your backend asking "anything new?" every few seconds, it only works when there is work to do. For a product with hundreds of connections, that is the difference between a calm queue and thousands of useless requests per minute.
Setup: one URL, or one URL per event
The shortest path is to set the URL when you create the connection (the webhookUrl field) or later, with POST /api/v1/sessions/{sessionId}/webhook. When you want to choose which events you receive, use the full configuration endpoint:
curl -X POST https://api.d-api.cloud/api/v1/sessions/my-session/webhook-config \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"type": "per_event",
"events": {
"messages.received": { "enabled": true, "webhookUrl": "https://app.yourcompany.com/wa/k7f2q9/messages" },
"connection.status": { "enabled": true, "webhookUrl": "https://app.yourcompany.com/wa/k7f2q9/connection" }
}
}'The two modes serve different needs:
single: every enabled event goes to the same URL. Good to get started, or when a single service receives everything and distributes it internally.per_event: each event has its own destination. Useful when support, monitoring and analytics are separate services.
Disabled events are not sent, so only turn on what you will actually process.
The envelope and the events you will use first
Every event arrives in the same envelope: event (the event name), sessionId (which connection produced it), data (the event-specific content), timestamp in ISO 8601, and traceId, which is worth keeping in your logs to cross-reference with support. The headers are Content-Type: application/json and User-Agent: Deliverify-Webhook/1.0.
| Event | When it fires | Typical use |
|---|---|---|
messages.received | A message arrived (or you sent one from the phone) | Inbox, chatbot, CRM |
messages.sent | A message was sent through the API | Log the sending history |
message.delivered / message.read | Delivered to and read by the recipient | Delivery status in your UI |
connection.status | Status changed to connected, disconnected or logged_out | Alert the customer before they notice |
groups_participants.join and related | Someone joined, left or was promoted in a group | Member management |
A detail that trips up a lot of people: there is no separate edit event. An edited message arrives as messages.received with type: "edited_message".
The payload of an incoming message
This is the event that matters most. The data object carries the message id, the type (text, image, audio, list reply and others), the content in message, the sender in from, whether it came from a group in is_group and, for media, the link in media_url:
{
"event": "messages.received",
"sessionId": "customer-042",
"timestamp": "2026-01-24T22:51:32.601Z",
"traceId": "c17dee440402792623e3ad6d925cb000",
"data": {
"id": "AC9831DDA691236BA3CE4909A187B703",
"type": "text",
"message": "Can we move it to Friday?",
"timestamp": 1769295092000,
"fromMe": false,
"is_group": false,
"from": { "jid": "[email protected]", "name": "Emily" },
"from_name": "Emily",
"media_url": null
}
}Watch fromMe: it is true when someone replied from the connected number's own phone. If your bot reacts to every message, filter this case so it does not answer itself.
Delivery policy: what the API guarantees and what is on you
On D-API, if your endpoint fails, delivery is retried up to 7 times with exponential backoff, at intervals of roughly 5s, 10s, 20s, 40s, 80s and 160s. Each attempt waits at most 30 seconds for a response. 404 and 410 responses are treated as permanent failures and stop the retries. There is also a per-URL circuit breaker and event deduplication with a 5-minute window.
In practice, a short outage on your server or a slow deploy does not make you lose messages. A route deleted by mistake, returning 404, does. That is why you should monitor the error rate of your webhook endpoint like any other critical route.
Webhook receiver checklist
- Return 2xx in milliseconds. Put the event on a queue and return
200. Calling an LLM, a CRM or a heavy database query before responding blows past the 30 seconds and triggers a retry. - Be idempotent on
data.id. The API's deduplication covers short windows; a retry can arrive later. Store processed IDs and ignore repeats. - Protect the URL. Since there is no HMAC, put a long random token in the path, check the
User-Agentand reject anysessionIdthat does not exist in your database. - Do not return 404 during a deploy. If the route disappears for a few seconds, retries stop. Prefer 503, which stays in the retry cycle.
- Log the
traceId. No personal data in logs, just the event identifier.
app.post('/wa/:token/messages', async (req, res) => {
if (req.params.token !== process.env.WA_WEBHOOK_TOKEN) return res.sendStatus(401)
const { event, sessionId, data, traceId } = req.body
await queue.add('wa-event', { event, sessionId, data, traceId }, { jobId: data.id })
res.sendStatus(200)
})Using the message ID as the jobId makes the queue itself drop duplicates. The real processing (replying, saving, calling the AI) happens in the worker. It is the same design used in a WhatsApp AI chatbot.
When to swap HTTP for RabbitMQ
If your volume is high or you already run a broker, D-API can publish events straight to your own RabbitMQ, configured per connection at /sessions/{sessionId}/rabbitmq-config. Some history sync events only exist on that channel. See the RabbitMQ integration page.
To understand the full path, from connecting the number to the first event, read how the WhatsApp API works. If your product will receive events from many customers at once, the per-connection design is covered in multiple numbers and on the WhatsApp API for SaaS page.
