6.1 WhatsAppWebhookController
Route: api/v{version:apiVersion}/notifications · Class attributes: [AllowAnonymous] · 2 endpoints
The only controller in this reference that does not require a Shumoul auth token — Meta calls it directly. Security is provided by webhook signature validation instead (see §8 Webhooks and §11 Security).
GET webhook
Purpose: Meta's one-time webhook URL verification handshake, run when a developer configures or
re-verifies the webhook URL in the Meta App Dashboard.
Authorization: none ([AllowAnonymous]) · Permission: none
Request (query string):
GET /api/v1/notifications/webhook?hub.mode=subscribe&hub.verify_token=ShumoulWhatsAppWebhook2026&hub.challenge=1158201444
Response (success): 200 OK, plain-text body = the hub.challenge value, echoed verbatim.
Response (failure): 403 Forbidden if hub.verify_token does not match the configured VerifyToken.
Error cases:
- Missing/incorrect
hub.verify_token→403, no body. hub.modeother thansubscribe→ treated as failure per Meta's own contract.
Backend service: none — compares directly against WhatsAppCloudApiOptions.VerifyToken.
Calls Meta API: No.
Angular/Flutter usage: Not applicable — this endpoint is called only by Meta's servers during webhook
configuration, never by a Shumoul client application.
POST webhook
Purpose: receives every inbound WhatsApp event from Meta — new messages and delivery/read status updates.
Authorization: none ([AllowAnonymous]) · Permission: none
Request: raw JSON body (Meta's webhook payload shape — see §8 Webhooks)
plus the X-Hub-Signature-256 header. Example (inbound text message):
{
"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" }
}]
}
}]
}]
}
Response: always 200 OK for a structurally valid, signature-valid request — regardless of whether any
individual message/status item fails to process. 403 Forbidden only on signature validation failure.
Error cases:
- Missing or invalid
X-Hub-Signature-256→403(whenSignatureValidationEnabledistrue). - Malformed JSON → parser returns an empty
WhatsAppWebhookParseResult, controller still returns200(Meta must not receive a retry-triggering non-2xx for a payload it will only ever send once, malformed or not). - A message referencing an unknown/deleted conversation phone number → a new
WhatsAppConversationis created; processing does not fail.
Backend service: IWhatsAppSignatureValidator.Validate → IWhatsAppWebhookParser.ParseAsync →
IWhatsAppInboxService.ProcessWebhookMessageAsync / ProcessWebhookStatusAsync →
INotificationDeliveryReceiptService.UpsertAsync.
Calls Meta API: No (this endpoint only receives; it never calls out to Meta).
Angular/Flutter usage: Not applicable — this endpoint is called only by Meta's servers. Frontend clients
observe the results of webhook processing via SignalR and the Inbox REST endpoints, not
this endpoint directly.
