Documentation
Webhooks
Register an HTTPS endpoint with POST /webhooks and the events you want. Each event becomes a delivery: a POST with a JSON body and three headers. The signing secret is returned once, on creation. The URL must be https and point to a public host: private network, loopback and link-local addresses are rejected, so use a tunnel to test on your machine.
Events
- message.received: a customer sent a message. Carries wamid, from, phoneNumberId, type, text, media fields, replyPayload, flowResponse, order and the Zaiped messageId for GET /media.
- message.status: a message you sent was delivered, read or failed, with error and pricing.
- template.status: Meta's verdict on a template.
- number.update: quality, messaging limit or display name changes.
- account.notice: WABA-level notices, including lost access.
- call.event: voice call events and statuses.
The body
{
"id": "YOUR_DELIVERY_ID",
"event": "message.received",
"createdAt": 1757160000000,
"data": { "wamid": "wamid.HBg...", "from": "+5511999999999", "type": "text", "text": "Hi", "messageId": "..." }
}Headers and signature
- X-Zaiped-Event: the event name.
- X-Zaiped-Delivery: the delivery id, the same across every attempt of that delivery. Use it to deduplicate.
- X-Zaiped-Signature: t=TIMESTAMP,v1=HEX, where v1 is HMAC-SHA256(secret, "TIMESTAMP.RAW_BODY").
// Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const [t, v1] = header.split(",").map((part) => part.slice(part.indexOf("=") + 1));
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}One delivery per event
Meta redelivers the same event whenever it is unsure we received it, and we absorb that: one source event produces ONE delivery per endpoint, with a single X-Zaiped-Delivery. What repeats is the attempt of the SAME delivery, when your endpoint does not answer 2xx, and then the id is the same. Still store the ids you processed and skip repeats: that is the net for the rare event with no stable identity, which we would rather deliver twice than swallow.
Retries
Answer 2xx quickly. Anything else, or a timeout of ten seconds, schedules a retry after one minute, then five, thirty, two hours and twelve hours. That is six attempts in total (the first one plus the five waits); after them the delivery is marked failed and stays in the log for thirty days. GET /webhooks/{webhookId}/deliveries lists the recent ones; POST .../test sends a test event.