Skip to content
Customer Pro

Developer

Outbound webhooks

Signed event POSTs to your endpoint whenever something happens in your workspace.

On this page

A webhook is a URL you own that Customer Pro calls. When something happens in your workspace — a customer replies, a message is read, a contact is created — the platform queues an HTTP POST to that URL with a JSON body describing the event, signed so you can prove it came from us.

Audio
Getting events sent to your system

Registering an endpoint #

  1. Name it and give the URL

    The URL must be http or https and reachable from the public internet.
  2. Tick the events

    At least one is required. message.received is pre-selected because it is what most integrations want.
  3. Copy the signing secret

    It appears once, immediately after creation, and is never shown again. Without it you cannot verify any delivery.
  4. Send a test

    The Test action queues one delivery to your endpoint carrying "test": true in its data, so you can confirm the plumbing before real traffic arrives.

Only owners and admins can register, edit, test or delete webhooks. Everyone else sees the list read-only.

The ten event types #

Field What it does
message.received An inbound message arrived from a customer.
message.sent An outbound message was accepted by WhatsApp.
message.delivered WhatsApp confirmed delivery to the handset.
message.read The recipient opened the message.
message.failed WhatsApp rejected or could not deliver the message.
phone.verified A phone number completed verification.
conversation.created A new conversation thread was opened.
conversation.updated An existing thread changed — status, assignment or last activity.
contact.created A contact was added to your workspace.
contact.updated An existing contact's details changed.
lead.created Someone submitted a Facebook lead ad form on a connected page. Fires after the lead has been read back from Meta, so data.fields carries the answers, not just an id.

That list is fixed; anything outside it is rejected when the webhook is saved.

What arrives at your endpoint #

Every delivery is a JSON POST with three headers worth reading: X-Webhook-Event names the event, X-Webhook-Signature carries the signature, and the user agent is WhatsAppBusiness-Webhook/1.0. The body always has the same envelope — the event name, an ISO 8601 timestamp, your organization id, and a data object whose shape depends on the event.

{
  "event": "message.received",
  "timestamp": "2026-01-30T09:14:22+00:00",
  "organization_id": 12,
  "data": { }
}

Verifying the signature #

The signature is an HMAC SHA-256 of the raw request body, keyed by your webhook's signing secret. Compute the same thing on your side and compare with a constant-time function. Do this before you parse or trust anything in the payload — an unverified POST is just a stranger's HTTP request.

$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, $secret);
$given = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

if (! hash_equals($expected, $given)) {
    http_response_code(401);
    exit;
}

Delivery, retries and timeouts #

Any 2xx response counts as delivered and clears the webhook's last error. Anything else — a 500, a connection failure, or no answer inside the timeout — is recorded as a failed attempt and retried with an exponential backoff of 2, 4, then 8 seconds.

Field What it does
retry_count How many retries follow a failed first attempt. Defaults to 3, and may be set between 0 and 5.
timeout_seconds How long we wait for your response before giving up on an attempt. Defaults to 10, and may be set between 5 and 30.

The registration form uses those defaults; both values can be changed on an existing webhook. Because retries are queued rather than run inline, a slow endpoint never blocks the message that triggered the event — but it does mean deliveries only leave once a queue worker is running.

The list shows a running Deliveries count per webhook, and the most recent failure in red under the status as Last error. That string is the HTTP status we got back, or the connection error we hit — the first place to look when events stop arriving. Disabling a webhook stops delivery without deleting its history; deleting it is permanent.

Muting a webhook for one contact #

Some contacts should not be mirrored into your own systems — an internal tester, a VIP whose thread stays inside the inbox, a number under a data request. Per-contact preferences let you switch one webhook off for one contact while leaving it on for everyone else.

These are session-authenticated JSON endpoints rather than a screen. A GET to developer/contacts/{contact}/webhook-preferences returns every active webhook with its is_enabled flag for that contact, and a PATCH to developer/contacts/{contact}/webhook-preferences/{webhook} with is_enabled sets it.

Where to go next #

Frequently asked

How do I verify a webhook came from you?
Every request carries an HMAC SHA256 signature computed over the raw request body using your webhook secret. Recompute it on your side and compare using a constant-time comparison.
Are these the same as the Meta webhook on my WhatsApp number?
No. The Meta webhook delivers inbound WhatsApp messages into this platform. These outbound webhooks deliver events from this platform to your own endpoint. They are configured in different places.
What happens if my endpoint is down?
Delivery is retried according to the retry count configured on the webhook, and the last error is recorded so you can see why it failed.