Event webhooks

Subscribe a URL to things that happen on WhatsApp: a customer writes in, a chatbot hands a chat to a person, a customer rates a conversation, a payment you asked for is paid or expires. Every call is logged and retried until it arrives.

Set it up

Go to Settings, Integrations, API Access, Webhooks, add your URL and tick the events you want. Each subscription gets its own signing secret, shown once. You can have several subscriptions, for example one per system.

Every call is a POST with a JSON body and these headers:

  • X-Wamafy-Signature, to check the call came from us.
  • X-Wamafy-Event, the event name, also in the body as event.
  • X-Wamafy-Event-Id, the event's id, also in the body. It stays the same on every retry.
  • X-Wamafy-Attempt, 1 on the first call, higher on a retry.

The event's details are inside data.

The test button on that page sends webhook.test, with a short message in data. Answer it like any other call.

message.inbound

A customer sent a message to one of your numbers.

{
  "id": "3f8a1c5e-7b2d-4e9f-a1c3-5e7b9d1f3a26",
  "event": "message.inbound",
  "occurredAt": "2026-10-07T10:00:00.000Z",
  "data": {
    "messageId": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAEhgUM0E",
    "from": "919876543210",
    "fromName": "Priya",
    "whatsappNumberId": "6f1c2a9e-1b3d-4c7a-9e2f-7d4b8a1c0e55",
    "type": "text",
    "text": "Is the blue one in stock?",
    "interactiveReplyId": null,
    "mediaId": null,
    "mediaCaption": null,
    "conversationId": "2b7e4f10-8c3a-4d2e-b1f6-0a9c5e7d3b21",
    "leadId": "9d3f6b2a-4e1c-4a8b-9f0d-2c7e5a1b3d64",
    "referral": null,
    "sentAt": "2026-10-07T09:59:58.000Z"
  }
}
  • type is the WhatsApp message type: text, image, interactive and so on. Fields that do not apply are null, never missing.
  • When they tap one of your buttons or list rows, interactiveReplyId is the id you gave it.
  • referral describes the ad, on the first message after someone taps a Click to WhatsApp ad.
  • sentAt is when the customer sent it; occurredAt is when we called you.
  • This opens the 24-hour window, so you can reply freely.

conversation.handed_off

A chatbot reached a hand-off step: it decided a person should take over. A good one to turn into a ticket or an alert.

{
  "id": "8b2d4f6a-1c3e-4a5b-9d7f-2a4c6e8b0d13",
  "event": "conversation.handed_off",
  "occurredAt": "2026-10-07T10:02:00.000Z",
  "data": {
    "conversationId": "2b7e4f10-8c3a-4d2e-b1f6-0a9c5e7d3b21",
    "leadId": "9d3f6b2a-4e1c-4a8b-9f0d-2c7e5a1b3d64",
    "contactPhone": "919876543210",
    "contactName": "Priya",
    "whatsappNumberId": "6f1c2a9e-1b3d-4c7a-9e2f-7d4b8a1c0e55",
    "flowId": "7a1b3c5d-2e4f-4a6b-8c0d-1e3f5a7b9c2d",
    "runId": "c4e6a8b0-1d3f-4a5c-9e7b-2d4f6a8c0e19",
    "note": "Customer asked about a refund",
    "assignedToUserId": null,
    "teamId": null,
    "handedOffAt": "2026-10-07T10:02:00.000Z"
  }
}
  • note is the text written on the hand-off step, if any.
  • assignedToUserId and teamId say who the step handed it to. Both are null when it went to the general queue.
  • Unless the step is set to keep the chatbot running, the chatbot then stays out of the chat, so your replies are not talked over.

csat.received

A customer tapped a rating on the satisfaction survey sent after a chat.

{
  "id": "5c7e9a1b-3d5f-4b6a-8c0e-4b6d8f0a2c35",
  "event": "csat.received",
  "occurredAt": "2026-10-07T10:30:00.000Z",
  "data": {
    "rating": 2,
    "conversationId": "2b7e4f10-8c3a-4d2e-b1f6-0a9c5e7d3b21",
    "leadId": "9d3f6b2a-4e1c-4a8b-9f0d-2c7e5a1b3d64",
    "contactPhone": "919876543210",
    "contactName": "Priya",
    "whatsappNumberId": "6f1c2a9e-1b3d-4c7a-9e2f-7d4b8a1c0e55",
    "assignedToUserId": null,
    "ratedAt": "2026-10-07T10:29:58.000Z"
  }
}

rating is 1 to 5. assignedToUserId is whoever was handling the chat, or null.

payment.paid

A payment you asked for with Ask for a payment was paid. It is confirmed with WhatsApp's own record of the payment before we call you, so you can fulfil the order.

{
  "id": "1e3a5c7e-9b2d-4f6a-8c1e-3a5c7e9b1d48",
  "event": "payment.paid",
  "occurredAt": "2026-10-13T10:01:12.000Z",
  "data": {
    "id": "5d7f9b1c-3e5a-4c7e-9a1b-3c5e7a9b1d24",
    "referenceId": "wp-mv5jrp6d-aba359af",
    "amount": 499,
    "currency": "INR",
    "to": "919876543210",
    "expiresAt": "2026-10-13T10:30:00.000Z",
    "refundedAmount": 0,
    "refunds": [],
    "createdAt": "2026-10-13T10:00:00.000Z",
    "status": "paid",
    "paymentId": "pay_Tn5LuVrMQaMghT",
    "paidAt": "2026-10-13T10:01:10.000Z"
  }
}
  • data is the payment as Get a payment returns it: match it to your order by data.id, the id Ask for a payment gave you.
  • paymentId is your payment gateway's id for the payment.

payment.expired

A payment you asked for was not paid in time: 30 minutes for a message, 3 days for a template. It can no longer be paid; ask again if you still want it.

{
  "id": "9d1f3b5a-7c2e-4a6d-8f0b-1c3e5a7d9f62",
  "event": "payment.expired",
  "occurredAt": "2026-10-13T10:35:00.000Z",
  "data": {
    "id": "5d7f9b1c-3e5a-4c7e-9a1b-3c5e7a9b1d24",
    "referenceId": "wp-mv5jrp6d-aba359af",
    "amount": 499,
    "currency": "INR",
    "to": "919876543210",
    "expiresAt": "2026-10-13T10:30:00.000Z",
    "refundedAmount": 0,
    "refunds": [],
    "createdAt": "2026-10-13T10:00:00.000Z",
    "status": "expired",
    "paymentId": null,
    "paidAt": null
  }
}

Delivery

  • Answer with a 2xx as soon as you have the body, then do the work. Anything else, or no answer within 10 seconds, is a failure.
  • A failed call is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours: six attempts in all, with the same body and the same id. So a call can arrive more than once: keep the ids you have handled and ignore a repeat.
  • Every call is logged for 30 days. Open Recent deliveries on the webhook to see what was sent, the answer and the next retry, and Resend one that failed or that you need again.
  • After 15 failed deliveries in a row (a delivery fails once all six attempts have), a subscription switches itself off. Switch it back on from the same page once your endpoint is fixed.
  • Message delivery updates are not events. They have their own status webhook.
    Event webhooks - Wamafy API