Webhooks

Overview

Outbound webhooks let WaDesk push events out to your own systems in real time — a CRM, a data warehouse, Zapier, Make, n8n, or any HTTPS endpoint. Whenever an event you've subscribed to happens in your workspace, WaDesk sends a JSON request to your URL, optionally signed so you can verify it really came from WaDesk.

Manage them under More → Webhooks. Webhooks are shared across the whole workspace: every member sees every endpoint, no matter who created it. When an event happens, WaDesk delivers it to every active endpoint in that workspace that subscribed to the event (or to the wildcard *).

Plan limits. Outbound webhooks are a paid-plan feature, and each plan allows a set number of endpoints. If your plan doesn't include the feature, or you've hit your limit, creating a new endpoint is blocked with an upgrade prompt. You can still edit and pause/resume the endpoints you already have.
Two different directions — don't mix them up. This page is about webhooks WaDesk sends to you. The webhook that Meta or Twilio sends to WaDesk (incoming messages, delivery receipts) is a single endpoint set up once by your administrator — see Inbound vs Outbound at the bottom.

Creating an Endpoint

Click Add a webhook on the index and fill in the form. The right rail shows a live sample payload and a Test fire action so you can wire up and validate your receiver before any real event arrives.

FieldRequiredNotes
Webhook URLYesThe address WaDesk sends to. Must be a valid URL — use HTTPS. Stored encrypted.
MethodNoThe HTTP method used to deliver — POST (the default) or PUT.
Internal nameNoA label to recognise the endpoint, e.g. “Production CRM relay”. Up to 191 characters.
EnvironmentNoA free-text tag for organising endpoints (defaults to Production).
EventsYesPick at least one event. See Event Types.
Signing secretNo (recommended)Used to sign each delivery so you can verify it. Stored encrypted. See Signing & Verification.
StatusNoWhether the endpoint is active (receiving events) when you save. On by default.
Everything sensitive is encrypted. The URL, the secret, and the event list are all encrypted in the database and only unlocked in memory at the moment a delivery is sent — so a database leak won't expose your endpoints or secrets.

Event Types

These are the events you can subscribe an endpoint to. Subscribe to any subset — an endpoint only receives the events in its list. The event name is what appears in the event / eventType fields of every payload.

EventFires when
message_receivedAn inbound message is received from a contact.
message_sentAn outbound message is accepted for sending.
message_deliveredAn outbound message is delivered to the recipient's device.
message_readAn outbound message is read (recipient has read receipts on).
message_failedAn outbound message fails to send — payload carries the failure reason.
broadcast_createdA broadcast is created.
broadcast_status_updatedA broadcast's overall status changes.
broadcast_message_status_updatedA single broadcast recipient's message changes status.
campaign_createdA campaign is created.
campaign_status_updatedA campaign's status changes.
campaign_contact_status_updatedA campaign contact's status changes.
campaign_contact_clickedA campaign contact clicks a tracked link.
campaign_contact_repliedA campaign contact replies.
contact_opt_inA contact's subscription state changes — opt-in or opt-out (a STOP keyword, or the unsubscribe toggle on a contact). The payload carries opted_in (boolean), an action of unsubscribed / resubscribed, and a source.
contact_updatedA contact record is edited (from the contact detail screen).
device_status_updatedA device's connection status changes (connected / disconnected / pairing).
Every event above is live. All sixteen fire the moment the action happens. The delivery to your endpoint is sent after the action finishes, so a slow receiver on your end never delays a send, an incoming message, or a status update inside WaDesk.
Wildcard. Subscribe an endpoint to * to receive every event type, including any added in future releases. Use it as a catch-all; use an explicit list when your receiver only handles specific events.

Delivery Payload

Every delivery is a JSON body with the same outer "envelope" for all events; the event-specific fields live under data. The request is sent with Content-Type: application/json.

Envelope

KeyTypeMeaning
idstring (UUID)A unique ID for this delivery. Use it for idempotency / de-duplication.
eventstringThe event name, e.g. message_delivered.
eventTypestringThe same value as event (provided for receivers that key on either name).
createdstring (ISO-8601)When WaDesk built the payload, e.g. 2026-05-29T18:32:08+00:00.
timestampinteger (Unix)Event time in seconds. Mirrors the upstream event time when one is available, else the build time.
dataobjectThe event-specific payload (fields vary by event).

Example — message_delivered

A real delivery looks like this. The data block for the message_* family carries the message identity, the recipient, the WhatsApp message id (wamid), and — on failure — the typed error code and reason:

POST /your/endpoint HTTP/1.1
Host: api.yourbrand.example
Content-Type: application/json
X-WaDesk-Signature: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

{
  "id": "3a8f7e2c-91b5-4d0f-a7a1-d6c8e4b9f2c0",
  "event": "message_delivered",
  "eventType": "message_delivered",
  "created": "2026-05-29T18:32:08+00:00",
  "timestamp": 1780000328,
  "data": {
    "workspace_id": 42,
    "user_id": 7,
    "message_id": 918273,
    "wamid": "wamid.HBgLOTE5ODc2NTQzMjEwFQIAERgSM0E...",
    "recipient": "+919876543210",
    "status": "delivered",
    "timestamp": 1780000328,
    "error_code": null,
    "error_reason": null,
    "pricing": { "billable": true, "category": "marketing" },
    "conversation": { "id": "abc123...", "origin": { "type": "marketing" } }
  }
}
On a failed send (message_failed) the same data shape carries status: "failed" with error_code and error_reason populated from the provider. Broadcast events add a broadcast_id / broadcast_name and an aggregate block with running sent / delivered / read / failed / clicked / total counters. Always code defensively — treat unknown keys as optional.

Signing & Verification

If you set a signing secret on an endpoint, every delivery includes this header:

X-WaDesk-Signature: <hex>

The value is HMAC-SHA256 computed over the exact JSON request body (the whole envelope, not just data), using your endpoint's secret as the key, encoded as lowercase hex. If no secret is set, the header is omitted entirely.

Verifying on your side

Recompute the HMAC over the raw request body (before any JSON parsing or re-serialisation) and compare it to the header in constant time:

# Pseudo-code — works in any language
secret   = "the secret you configured on the endpoint"
raw_body = read_raw_request_body()          # bytes, exactly as received
expected = hex( hmac_sha256(secret, raw_body) )
given    = request.header("X-WaDesk-Signature")

if not constant_time_equals(expected, given):
    return 401                              # reject — do not process
process(json_parse(raw_body))               # safe to handle now
// Node.js (Express, raw body required)
const crypto = require('crypto');
const expected = crypto
  .createHmac('sha256', SECRET)
  .update(req.rawBody)                       // Buffer of the raw body
  .digest('hex');
const given = req.get('X-WaDesk-Signature') || '';
const ok = expected.length === given.length &&
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given));
if (!ok) return res.status(401).end();
// PHP (mirrors how WaDesk signs it)
$expected = hash_hmac('sha256', $rawBody, $secret);
$given    = $_SERVER['HTTP_X_WADESK_SIGNATURE'] ?? '';
if (!hash_equals($expected, $given)) { http_response_code(401); exit; }
Verify before you parse. Always compute the HMAC on the raw body bytes — re-serialising the parsed JSON can reorder keys or change whitespace and break the signature. Compare in constant time. Treat a missing or mismatched signature as a failed request and discard it. Keep the secret confidential: anyone who has it can forge valid-looking deliveries.

Delivery, Retries & Health

Each delivery is a single HTTP request with a 10-second timeout. A delivery counts as successful on any 2xx response; anything else (a 4xx, a 5xx, a timeout, a DNS failure, or a connection error) counts as a failure. For every attempt, WaDesk records the status code, how long it took, the response body, and any error text, and updates the endpoint's success and failure counts plus when it last fired.

An endpoint is automatically marked as failing after three failures in a row. It keeps receiving events while failing — the flag is just a health warning, not a pause. To actually stop deliveries, pause the endpoint.

StateMeaning
activeEnabled and not flagged — delivering normally.
failingEnabled but recent deliveries are erroring. Still receives events.
pausedDisabled — receives no events at all.
Design your receiver to be idempotent and fast. Respond 2xx immediately and do heavy work asynchronously on your side. De-duplicate on the envelope id — the same logical event can be re-delivered, and inbound provider retries upstream can produce more than one notification. A slow endpoint inflates your p95 latency and risks the failing flag.

Monitoring & Analytics

The Webhooks index gives you a live health overview across all endpoints in the workspace:

  • Endpoints — total configured, plus active / paused counts.
  • Events fired (24h) — deliveries in the last day.
  • Success rate — percentage of 2xx responses over the last 24h.
  • Latency p95 — 95th-percentile delivery latency.
  • Event mix — the top events by volume over 24h.
  • Recent deliveries — a feed of the latest attempts with their status codes.

Opening a single endpoint shows the last 7 days of deliveries: a daily success-vs-failure chart, status-code buckets (2xx / 4xx / 5xx / other), p95 latency, retry and failure counts, and a table of recent deliveries with their response details. You can filter the index by state (all / active / paused / failing) and search by URL, name, or event.

Managing & Test-firing Endpoints

For each endpoint you can:

  • Test fire — send a sample payload (a test_fire event with a friendly greeting in data.message) and see the live status code and latency the same way a real event would record them. It signs the request with your secret too, so it validates the full path including verification.
  • Toggle — pause or resume the endpoint.
  • Edit — change the URL, events, secret, method, name, or environment.
  • Delete — remove the endpoint and stop all deliveries to it.
Tip. After editing an endpoint, run Test fire to confirm your receiver still accepts the payload and the signature before relying on live events. A successful test fire records a normal delivery row, so it also appears in the analytics.

Inbound vs Outbound

These are two completely different things — keep them straight:

Outbound webhooks (this page)Inbound platform webhook
DirectionWaDesk → your systemsMeta / Twilio → WaDesk
Who configuresYou, per workspace, under More → WebhooksThe platform administrator, once
What it carriesYour subscribed lifecycle eventsInbound WhatsApp messages + delivery / read / failed receipts from the provider
EndpointAny URL you ownA single fixed WaDesk URL (below)

The inbound platform webhook is one shared endpoint that both Meta and Twilio post to (WaDesk detects which provider by the request shape):

https://YOUR-DOMAIN/webhooks/whatsapp/inbound
  • Meta (Cloud API / WABA). Paste this URL in your Meta app dashboard under WhatsApp → Configuration → Callback URL, set the Verify token to the value saved in admin settings, and subscribe to the messages and message_status fields. Meta first does a GET verification handshake (it echoes back its challenge only if the verify token matches), then signs every POST with X-Hub-Signature-256, which WaDesk validates against the configured Meta App Secret.
  • Twilio. Paste the same URL as the inbound / status callback on your Twilio Messaging Service. Twilio signs each request with X-Twilio-Signature, which WaDesk validates against your Twilio Auth Token.
Where to set the provider side. The verify token, Meta App Secret, the Unofficial API Server URL setting, and Twilio credentials all live on the admin System Settings → WhatsApp provider configuration page, which also displays this exact callback URL with a copy button. You do not need an outbound webhook to receive inbound messages — that is what the inbound endpoint is for.
WaDesk Documentation