Webhooks
Webhook payload modes
Choose Yanola normalized events or a Meta-compatible payload shape for migration-oriented integrations.
What this guide covers
- Prefer normalized mode for stable Yanola public IDs and consistent envelopes.
- Use Meta-compatible mode when adapting an existing direct Meta integration.
- Treat compatibility as a payload shape option, not direct Meta delivery.
Choose one payload mode per endpoint
Yanola renders each delivery according to the endpoint's configured payload mode and identifies that format in yanola-payload-format. The event identity and delivery controls remain Yanola-managed in every mode.
| Mode | Body | Best fit |
|---|---|---|
yanola | Stable Yanola Event envelope with normalized public data. | New integrations and long-term Yanola contracts. |
meta | Unwrapped Meta-compatible WhatsApp webhook JSON shape. | Migration of an existing Meta-shaped handler. |
yanola_with_raw | Normalized Event plus nullable retained provider payload. | Audited integrations that explicitly need provider detail and accept retention limits. |
Normalized mode keeps public Yanola IDs, environment, event type, and data in one stable envelope and avoids coupling your application to provider payload changes.
Yanola-normalized payload
The yanola body is the same safe public Event envelope returned by client.events.retrieve. The data object can contain normalized message, Sender, and conversation projections.
{
"id": "evt_...",
"type": "whatsapp.message.received",
"environment": "live",
"createdAt": "2026-07-29T10:00:00.000Z",
"data": {
"message": {
"id": "msg_...",
"environment": "live",
"direction": "inbound",
"type": "text",
"status": "delivered",
"from": "+15551234567",
"to": "+15557654321",
"text": "Hello",
"createdAt": "2026-07-29T10:00:00.000Z"
},
"sender": {
"phoneNumber": "+15557654321",
"displayName": "Acme Support",
"status": "active",
"apiEligible": true
},
"conversation": {
"id": "conv_..."
}
}
}Meta-compatible payload
The meta mode produces an unwrapped WhatsApp Business Account shape with object, entry, changes, and value. Yanola narrows a retained Meta batch to the triggering Developer Event when possible; otherwise it synthesizes the same structural shape from normalized data.
It is not a byte-for-byte replay of Meta's original HTTP request. Yanola still supplies its own signature and correlation headers, so continue verifying yanola-signature rather than a Meta signature header.
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "waba_...",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15557654321",
"phone_number_id": "..."
},
"messages": []
}
}
]
}
]
}Normalized with retained raw data
yanola_with_raw adds a raw property to the normalized envelope. When retained data is available, raw contains provider, payloadType, and providerPayload.
raw is explicitly null when the provider payload was not retained or its entitlement-controlled retention window expired. Your handler must work correctly in both cases.
{
"id": "evt_...",
"type": "whatsapp.message.received",
"environment": "live",
"createdAt": "2026-07-29T10:00:00.000Z",
"data": {
"message": {
"id": "msg_..."
}
},
"raw": {
"provider": "meta_whatsapp",
"payloadType": "webhook",
"providerPayload": {}
}
}Do not make application correctness depend on raw retention. Apply least-privilege access, redaction, and your own deletion policy to any provider payload you persist.
Use SDK payload types
The SDK exports WebhookPayload<'yanola'>, WebhookPayload<'meta'>, WebhookPayload<'yanola_with_raw'>, and their named component types. Validate runtime JSON before treating untrusted input as one of these TypeScript types.
import type {
WebhookPayload,
WebhookPayloadFormat,
} from "@yanola/whatsapp"
function handlePayload<Format extends WebhookPayloadFormat>(
format: Format,
payload: WebhookPayload<Format>
) {
if (format === "yanola") {
console.log((payload as WebhookPayload<"yanola">).id)
}
}Forward-compatible parsing
- Route by yanola-payload-format and, in normalized modes, by event.type.
- Ignore unknown additive normalized fields.
- Do not assume every event includes message, sender, and conversation simultaneously.
- Treat raw as nullable and providerPayload as untrusted unknown data.
- Do not infer environment or replay state from body shape; read the signed payload and Yanola headers.