21. Webhook Payload Catalog
Every payload shape below is Meta's own published WhatsApp Cloud API webhook format. For each, this catalog
states exactly what WhatsAppWebhookParser extracts today, verified directly against
Core/Services/WhatsAppWebhookParser.cs — not assumed from Meta's schema alone. RawPayloadJson (the
complete, unmodified webhook JSON) is stored on every parsed message regardless of type, so no data is ever
lost — but only the fields called out as "Extracted" below are available as structured
WhatsAppWebhookMessageDto properties for your code to read without re-parsing the raw JSON yourself.
Parser mechanism (applies to every example below): the parser has exactly one type-specific branch
(text). Every other type falls through to one generic branch that looks for a JSON property named
literally after the type string and, if found, reads id/mime_type/sha256 from it. This means media
types (whose JSON object genuinely has those three keys) are extracted correctly "by coincidence of shape,"
while non-media types (location, contacts, button, interactive, reaction, system) are not
extracted at all — this is a real, verified characteristic of the current implementation, not a
documentation gap.
21.1 Message Received — Text
Extracted: TextBody (the only type-specific field extraction in the parser).
{
"object": "whatsapp_business_account",
"entry": [{
"id": "2091544401399393",
"changes": [{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": { "display_phone_number": "966501234567", "phone_number_id": "1088505757690326" },
"contacts": [{ "profile": { "name": "Ahmed Al-Otaibi" }, "wa_id": "966501112222" }],
"messages": [{
"from": "966501112222", "id": "wamid.HBgLOTY2NTAxMTEyMjIyFQIAEhggMTIzNA==",
"timestamp": "1751500000", "type": "text",
"text": { "body": "Hello, I have a question about my order" }
}]
}
}]
}]
}
21.2 Image
Extracted: MediaId, MediaMimeType, MediaSha256 (via the generic media-shape branch).
"messages": [{
"from": "966501112222", "id": "wamid.HBgLxx", "timestamp": "1751500001", "type": "image",
"image": { "id": "1234567890123456", "mime_type": "image/jpeg", "sha256": "a1b2c3...", "caption": "Payment receipt" }
}]
Note: caption exists in Meta's payload but has no dedicated DTO field — it is not extracted.
21.3 Audio
Extracted: MediaId, MediaMimeType, MediaSha256.
"messages": [{
"from": "966501112222", "id": "wamid.HBgLxx", "timestamp": "1751500002", "type": "audio",
"audio": { "id": "1234567890123457", "mime_type": "audio/ogg; codecs=opus", "sha256": "b2c3d4..." }
}]
21.4 Document
Extracted: MediaId, MediaMimeType, MediaSha256.
"messages": [{
"from": "966501112222", "id": "wamid.HBgLxx", "timestamp": "1751500003", "type": "document",
"document": { "id": "1234567890123458", "mime_type": "application/pdf", "sha256": "c3d4e5...", "filename": "contract.pdf" }
}]
Note: filename exists in Meta's payload but has no dedicated DTO field — it is not extracted.
21.5 Video
Extracted: MediaId, MediaMimeType, MediaSha256.
"messages": [{
"from": "966501112222", "id": "wamid.HBgLxx", "timestamp": "1751500004", "type": "video",
"video": { "id": "1234567890123459", "mime_type": "video/mp4", "sha256": "d4e5f6..." }
}]
21.6 Sticker
Extracted: MediaId, MediaMimeType, MediaSha256 (Meta's sticker object happens to share the same
id/mime_type/sha256 shape).
"messages": [{
"from": "966501112222", "id": "wamid.HBgLxx", "timestamp": "1751500005", "type": "sticker",
"sticker": { "id": "1234567890123460", "mime_type": "image/webp", "sha256": "e5f6a7...", "animated": false }
}]
21.7 Location
Extracted: nothing beyond MessageId/From/Type/Timestamp/ProfileName — no latitude/longitude
capture exists anywhere in the parser or DTO. The full payload survives only in RawPayloadJson.
"messages": [{
"from": "966501112222", "id": "wamid.HBgLxx", "timestamp": "1751500006", "type": "location",
"location": { "latitude": 24.7136, "longitude": 46.6753, "name": "Riyadh Office", "address": "King Fahd Rd, Riyadh" }
}]
21.8 Contacts (a message that IS a shared contact card)
Extracted: nothing beyond the base fields — no contact-card-specific extraction exists. This is a
distinct concept from the webhook-level value.contacts array (which is parsed, to resolve ProfileName
— see §8.7).
"messages": [{
"from": "966501112222", "id": "wamid.HBgLxx", "timestamp": "1751500007", "type": "contacts",
"contacts": [{ "name": { "formatted_name": "Sara Al-Harbi" }, "phones": [{ "phone": "+966502223333", "type": "CELL" }] }]
}]
21.9 Template Status Update (message_template_status_update)
This is a separate top-level webhook field (sibling of messages), not a message. It is logged only —
LogTemplateStatusChange reads message_template_name and event for a log line and produces no DTO;
it is never added to InboundMessages or StatusUpdates and is not persisted anywhere.
{
"object": "whatsapp_business_account",
"entry": [{ "id": "2091544401399393", "changes": [{
"field": "message_template_status_update",
"value": { "message_template_id": 123456, "message_template_name": "send_document_invoice", "message_template_language": "ar", "event": "APPROVED", "reason": null }
}] }]
}
21.10 Message Status — Delivery
Extracted into WhatsAppWebhookStatusDto: MessageId, RecipientId, Status ("delivered"), TimestampUnix.
"statuses": [{
"id": "wamid.HBgLxx", "recipient_id": "966501112222", "status": "delivered",
"timestamp": "1751500010", "conversation": { "id": "conv-abc", "origin": { "type": "business_initiated" } }
}]
21.11 Message Status — Read
"statuses": [{ "id": "wamid.HBgLxx", "recipient_id": "966501112222", "status": "read", "timestamp": "1751500020" }]
21.12 Message Status — Failed
Extracted: Status ("failed"), plus ErrorCode/ErrorMessage from only the first element of
errors[] — title and error_data.details are present in Meta's payload but have no DTO field and are
not extracted; a second or later error in a multi-error array is silently dropped.
"statuses": [{
"id": "wamid.HBgLxx", "recipient_id": "966501112222", "status": "failed", "timestamp": "1751500030",
"errors": [{ "code": 131026, "title": "Message undeliverable", "message": "Recipient phone number not in allowed list", "error_data": { "details": "..." } }]
}]
See §22 Meta Error Reference for what error code 131026 means and how
NotificationFailureClassifier handles it.
21.13 Reaction
Extracted: nothing beyond the base fields — Meta's reaction object (message_id, emoji) has no
matching DTO field; the emoji and the message it reacted to are recoverable only from RawPayloadJson.
"messages": [{
"from": "966501112222", "id": "wamid.HBgLxx", "timestamp": "1751500040", "type": "reaction",
"reaction": { "message_id": "wamid.HBgLyy", "emoji": "👍" }
}]
