ChatMaxima Docs

WhatsApp Webhooks - Delivery, Read and Reply Events

Get WhatsApp delivered, read, failed and reply events pushed to your server. Create webhook subscriptions by API and verify each event's HMAC-SHA256 signature.

Overview

WhatsApp webhooks push events to your server as they happen, so you do not have to poll for message status. ChatMaxima sends an HTTPS POST to your URL when a WhatsApp message you sent through the API is delivered, read or failed, and when the customer replies.

You manage subscriptions with the API: create one per endpoint, choose the events, and test it before going live.

Prerequisites

Events

EventSent when
deliveredThe message reached the customer's phone.
readThe customer opened the message, or played a voice note.
failedWhatsApp could not deliver the message after accepting it.
replyA customer you have messaged through the API sends you a message.
sentAccepted when subscribing. The send response already confirms sent, so rely on the response rather than this event.

Status events only move forward: you will not get delivered replacing a read. Messages that Meta rejects immediately do not produce a webhook, because the send request itself returns the error.

Create a webhook

curl -X POST https://chatmaxima.com/api/v2/whatsapp/webhooks/ \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "callback_url": "https://example.com/hooks/chatmaxima",
    "events": ["delivered", "read", "failed", "reply"],
    "account_id": "a1b2c3d4e5f6"
  }'
FieldTypeRequiredDescription
callback_urlstringYesYour HTTPS endpoint. It must resolve to a public IP address.
eventsarrayYesAny of sent, delivered, read, failed, reply.
account_idstringNoOnly send events for this WhatsApp account. Leave it out to cover all of them.

The response contains the signing secret. It is shown only once, so store it securely right away.

{
  "status": "success",
  "code": 201,
  "message": "Webhook created. Store the secret now; it is not shown again.",
  "data": {
    "webhook_id": "whk_0123456789abcdef",
    "callback_url": "https://example.com/hooks/chatmaxima",
    "events": ["delivered", "read", "failed", "reply"],
    "account_id": "a1b2c3d4e5f6",
    "status": "active",
    "secret": "whsec_XXXXXXXX"
  }
}

List, test and delete webhooks

ActionRequest
List webhooksGET /api/v2/whatsapp/webhooks/
Send a test eventPOST /api/v2/whatsapp/webhooks/{webhook_id}/test/
Delete a webhookDELETE /api/v2/whatsapp/webhooks/{webhook_id}/
  • List returns every subscription with its status (active or inactive). The secret is never shown again.
  • Test sends a signed webhook.test event to your URL right away and reports what your server answered: delivered, http_status and the start of the response body.
  • Delete deactivates the subscription and returns 204 with no body. Pending retries for it stop.

To change a URL or the event list, create a new webhook and delete the old one.

Event payloads

Status event

{
  "id": "evt_0123456789abcdef",
  "event": "message.delivered",
  "occurred_at": "2026-11-03T10:00:05+00:00",
  "data": {
    "message_id": "wamid.XXXXXXXX",
    "account_id": "a1b2c3d4e5f6",
    "template_name": "order_update",
    "broadcast_name": null,
    "reference_id": "order-1001",
    "status": "delivered",
    "error": null
  }
}

event is message.delivered, message.read or message.failed. For failed, error holds the report WhatsApp sent, including Meta's error code. For session messages, template_name and broadcast_name are empty strings.

Reply event

{
  "id": "evt_0123456789abcdef",
  "event": "message.reply",
  "occurred_at": "2026-11-03T10:01:00+00:00",
  "data": {
    "message_id": "wamid.INBOUND",
    "account_id": "a1b2c3d4e5f6",
    "mobile_no": "a1b2c3d4e5f6_15550001111",
    "text": "Yes, confirmed",
    "reference_id": "order-1001",
    "broadcast_name": "",
    "template_name": "order_update"
  }
}
  • mobile_no is ChatMaxima's contact reference: the account id, an underscore, then the customer's WhatsApp number (or their WhatsApp username id).
  • text is the message text, or the caption of a photo or file. It is empty for media sent without a caption.
  • reference_id, template_name and broadcast_name come from the most recent API message you sent this customer.

Verify the signature

Every request carries these headers:

HeaderValue
X-CM-EventThe event name, for example message.read.
X-CM-Signaturesha256= followed by the HMAC-SHA256 of the raw request body, keyed with your webhook secret.
User-AgentChatMaxima-Webhook/1.0

Compute the HMAC over the raw body bytes before parsing the JSON, and compare it in constant time:

const crypto = require('crypto');

function is_valid_signature(raw_body, signature_header, secret)
{
    const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(raw_body).digest('hex');
    const a = Buffer.from(expected);
    const b = Buffer.from(signature_header || '');
    return a.length === b.length && crypto.timingSafeEqual(a, b);
}
$raw_body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw_body, $webhook_secret);
$is_valid = hash_equals($expected, $_SERVER['HTTP_X_CM_SIGNATURE'] ?? '');

The signature does not include a timestamp, so store each event id and ignore ones you have already processed.

Delivery and retries

  • Timing: events are sent within about a minute.
  • Success: any 2xx response. Answer quickly and do slow work in the background. The request times out after 10 seconds.
  • Redirects are not followed, so a 3xx counts as a failure. Use your final URL.
  • Retries: a failed delivery is retried after 1 minute, 5 minutes, 30 minutes and 2 hours, for 5 attempts in total. A retry carries the same event id.
  • Ordering is not guaranteed. A retried delivered can arrive after read, so use the latest status your system has seen.

Errors

HTTPCodeWhen it happens
422MISSING_CALLBACK_URLcallback_url is missing.
422INVALID_CALLBACK_URLThe URL is not HTTPS, is localhost, does not resolve, or points to a private address.
422MISSING_EVENTSevents is missing or empty.
422INVALID_EVENTAn event name is not supported.
422INVALID_ACCOUNTaccount_id is not one of your WhatsApp accounts.
404NOT_FOUNDNo webhook with that webhook_id.

Authentication, plan and rate-limit errors are listed on the API overview.

Troubleshooting

My endpoint receives no status events

  1. Check the message was sent through the API. Messages sent from the Inbox or by bots without a webhook do not produce these events.
  2. For WhatsApp session messages, check the send included a reference_id.
  3. Run the test endpoint and look at http_status and the response it reports.

Signature checks fail

Make sure you hash the raw body exactly as received. Frameworks that parse and re-serialize JSON change the bytes and break the signature.

Next Steps

Nesta página