Push real-time events from WhatsPing to your own systems. Register a URL, pick the events you care about, and WhatsPing POSTs a signed JSON payload the moment something happens — new messages, delivery updates, campaign progress and account changes.
Delivered on a queue with automatic retries — every attempt is logged so you can see exactly what was sent.
From Developer → Webhooks in your dashboard. Create as many endpoints as you like — each has its own URL, event subscriptions and signing secret.
What you configure per endpoint.
Available under /developer/webhooks.
A webhook stores a name, url, subscribed events, an active flag and its secret. Toggle a webhook off any time to pause delivery without deleting it.
Every event is sent as an HTTP POST with a consistent JSON envelope and three headers. Respond with any 2xx to acknowledge.
# Common envelope for every event POST https://myapp.com/webhooks/whatsping X-Webhook-Event: message_received X-Webhook-Signature: 9f86d0818829... (HMAC-SHA256) Content-Type: application/json { "event": "message_received", "timestamp": "2026-07-26T12:00:00+00:00", "data": { /* event-specific — see catalog */ } }
| Header | Description |
|---|---|
| X-Webhook-Event | The event name (e.g. message_sent) — lets you route without parsing the body. |
| X-Webhook-Signature | HMAC-SHA256 of the raw JSON body, signed with your webhook secret. |
| Content-Type | Always application/json. |
Every request is signed so you can be sure it came from WhatsPing. Recompute the HMAC-SHA256 of the raw request body using your webhook's secret and compare it to the X-Webhook-Signature header.
// Laravel controller receiving the webhook $payload = $request->getContent(); // raw body $expected = hash_hmac('sha256', $payload, $webhookSecret); $received = $request->header('X-Webhook-Signature'); if (! hash_equals($expected, $received)) { abort(403, 'Invalid signature'); } // ✓ trusted — process $request->input('event') / 'data'
const crypto = require('crypto'); // express: use express.raw() so body is the exact bytes const expected = crypto .createHmac('sha256', webhookSecret) .update(req.body) // raw Buffer .digest('hex'); if (expected !== req.headers['x-webhook-signature']) { return res.status(403).send('Invalid signature'); } // ✓ trusted
import hmac, hashlib expected = hmac.new( webhook_secret.encode(), request.data, # raw body bytes hashlib.sha256 ).hexdigest() if not hmac.compare_digest(expected, request.headers['X-Webhook-Signature']): abort(403) # ✓ trusted
The signature covers the entire envelope — event, timestamp and data — so any tampering invalidates it. Keep your secret server-side only.
Subscribe a webhook to any of these. Events marked reserved can be selected now and will start delivering once emitting is enabled.
The data object inside the envelope, for each event that fires. Every payload also includes the top-level event and timestamp.
Fires when a contact sends you a message. The message_type mirrors the WhatsApp type (text, image, document, interactive…).
{
"event": "message_received",
"timestamp": "2026-07-26T12:00:00+00:00",
"data": {
"message_id": 184203,
"message_wamid": "wamid.HBgM...",
"contact_id": 5821,
"contact_name": "Sara Ahmed",
"phone": "8801712345678",
"message": "Hi! Is the offer still on?",
"message_type": "text",
"company_id": 979
}
}Fires when an outbound message is accepted by WhatsApp (via the API, a reply or a campaign).
{
"event": "message_sent",
"timestamp": "2026-07-26T12:00:05+00:00",
"data": {
"message_id": 184204,
"message_wamid": "wamid.HBgM...",
"contact_id": 5821,
"phone": "8801712345678",
"company_id": 979
}
}Fires when a message fails to send — includes the error reported by WhatsApp.
{
"event": "message_failed",
"timestamp": "2026-07-26T12:00:06+00:00",
"data": {
"message_id": 184205,
"contact_id": 5821,
"phone": "8801712345678",
"error": "More than 24 hours since the last reply",
"company_id": 979
}
}Fires as a message moves through delivery states: sent → delivered → read, or failed.
{
"event": "message_status_updated",
"timestamp": "2026-07-26T12:00:10+00:00",
"data": {
"message_id": 184204,
"message_wamid": "wamid.HBgM...",
"status": "delivered", // sent | delivered | read | failed
"error": null,
"contact_id": 5821,
"company_id": 979
}
}Fires when a campaign begins sending.
{
"event": "campaign_started",
"timestamp": "2026-07-26T09:00:00+00:00",
"data": {
"id": 4821,
"name": "Ramadan promo",
"total_contacts": 1250,
"created_at": "2026-07-26T09:00:00+00:00"
}
}Fires when a campaign finishes sending to all contacts.
{
"event": "campaign_completed",
"timestamp": "2026-07-26T09:14:00+00:00",
"data": {
"id": 4821,
"name": "Ramadan promo",
"total_contacts": 1250,
"completed_at": "2026-07-26T09:14:00+00:00"
}
}Fires when your WhatsApp Business account status changes (e.g. a review decision).
{
"event": "account_status_updated",
"timestamp": "2026-07-26T08:00:00+00:00",
"data": {
"decision": "APPROVED",
"account_status": "active",
"company_id": 979
}
}Fires on a display-name decision for your phone number, with the rejection reason if declined.
{
"event": "phone_number_name_updated",
"timestamp": "2026-07-26T08:05:00+00:00",
"data": {
"decision": "APPROVED",
"phone_number": "8801712345678",
"requested_name": "WhatsPing Store",
"rejection_reason": null,
"company_id": 979
}
}Every delivery attempt is recorded — the event, the payload sent, the response status and body, and any error — so you can debug and replay with confidence.
| Time | Event | Status | Response | Attempt |
|---|---|---|---|---|
| 12:00:10 | message_status_updated | 200 | { "ok": true } | 1 / 3 |
| 12:00:05 | message_sent | 200 | { "ok": true } | 1 / 3 |
| 12:00:00 | message_received | 200 | OK | 1 / 3 |
| 09:14:00 | campaign_completed | 503 | Service Unavailable | retrying · 2 / 3 |
Each log row stores event, payload, response_status, response_body and error. A 5xx or timeout is retried automatically with backoff.
Create a webhook, verify the signature, and start reacting to events in real time.