Receive webhooks

Rather than polling, give us a URL and we will POST events to it as they happen.

Set your callback URL

In the console, go to Settings → Console → Webhooks:

  1. Put your public HTTPS URL in Callback URL.
  2. Turn Deliver events on.
  3. Copy the webhook signing secret and store it with your other secrets. Your handler needs it to prove an event is ours.

There is no API for any of this — an API key cannot change where your events go.

Nothing is delivered until the endpoint is set, so no events are queued up waiting for it. Set it before you need it.

The events

EventFires when
message.sentWe handed the message to WhatsApp
message.deliveredIt reached the device
message.readThe user opened it
message.failedIt will not arrive
inbound.receivedThe user replied to you
verify.deliveredA code reached the user
verify.approvedA code was entered correctly
verify.failedA verification failed
template.status_changedMeta approved or rejected a template
template.category_changingMeta will change a template's category, usually in about a day
template.category_changedMeta changed a template's category, including during review
usage.thresholdA key passed 80% or 100% of its monthly limit

inbound.received matters more than it looks: there is no endpoint that reads replies, so this webhook is the only way to see one.

The two category events warn you before a template stops sending. template.category_changing carries category, newCategory and effectiveAt; template.category_changed carries previousCategory and category. template.status_changed carries category too, so an approval says which category Meta approved it as. A template Meta moves to MARKETING can no longer be sent through the API: a send returns template_category_not_allowed.

Every delivery is signed

The URL is public, and anyone who finds it can POST to it. Each delivery carries an X-Msgeasy-Signature header that proves it came from us, and your handler must check it before trusting the body.

How to check it, in each framework, is in Write a webhook handler.

The payload

JSON
{
  "id": "msg:0f3a...:delivered",
  "type": "message.delivered",
  "sequence": 2,
  "createdAt": "2026-09-06T10:35:00.000Z",
  "data": { "messageId": "msg_...", "status": "delivered", "whatsappMessageId": "wamid..." }
}

id is derived from the event, not random, so it is your dedupe key. sequence ranks the status. Both matter because events can arrive twice and out of order — Write a webhook handler shows how to handle that.

Delivery

Answer with a 2xx. A delivery that keeps failing is retried and then dropped — the schedule, and what to check when nothing arrives, are in Write a webhook handler.

If you need a second source of truth on a message, poll messages.get as a backstop.