Skip to main content
Version: Latest

13. Troubleshooting

Webhook verification fails

  • Confirm the hub.verify_token Meta sends matches WhatsAppCloudApi:VerifyToken exactly (case-sensitive, no surrounding whitespace).
  • Confirm the webhook URL registered in the Meta App Dashboard points at https://{your-domain}/api/v1/notifications/webhook and is reachable over HTTPS from the public internet (Meta will not call an unreachable or self-signed-cert URL).
  • Check GET Diagnostics/WhatsApp for a configuration warning if VerifyToken is empty.

Signature invalid

  • Confirm WhatsAppCloudApi:AppSecret matches the Meta App's actual App Secret (not the access token, and not the tenant's own WhatsAppOperationalSettings.AppSecret, which is a different value for a different purpose — see §4.3).
  • Confirm nothing between Meta and the app (a reverse proxy, a WAF, a body-parsing middleware) rewrites or re-encodes the request body — the signature is computed over the exact raw bytes Meta sent, and any transformation invalidates it.
  • If testing manually via Postman, ensure the HMAC is computed over the literal JSON text of the request body, not a re-serialized/pretty-printed version of it.

Token expired

  • Meta long-lived access tokens eventually expire or can be revoked. A 401/400 from Meta on any send surfaces as ErrorCode/ErrorMessage in the response, or as a Result<Guid>.FailureAsync message containing Meta's error text for Communication-module sends.
  • GET Health reports isConfigured: true even for an expired token — configuration presence and token validity are different checks. Use POST Test/WhatsApp or the smoke test to verify the token actually works, not just that it's set.

Wrong Graph API version

  • As of the Phase 1 Runtime Consolidation, there is exactly one place this is configured: WhatsAppCloudApi:GraphApiVersion (default v25.0). If a send fails with a Meta error referencing a deprecated or unsupported API version, confirm this key rather than looking for a per-service override — none exist anymore.

Phone number formatting

  • All phone normalization goes through WhatsAppPhoneHelper.TryNormalizePhone (see §4, §12.5). Accepted input shapes: 966XXXXXXXXX (already normalized), 0XXXXXXXXX (local), bare 5XXXXXXXX (9 digits), and any of these with +/spaces/dashes. Anything else fails normalization and the dispatch is skipped before any Meta call — check application logs for a "phone normalization failed" warning rather than assuming a Meta rejection.

Template rejected

  • Confirm the template name is spelled exactly as approved in Meta Business Manager (case-sensitive) and is approved for the specific languageCode being sent.
  • Confirm the number and type of components/parameters matches what the approved template expects — a mismatch is a common cause of rejection surfaced via ErrorCode/ErrorMessage.
  • Use POST Diagnostics/Providers or POST Preview to see exactly which template name and language the system resolved before the send was attempted.

Media send fails

  • Outbound media requires a publicly reachable URL (ImageUrl/DocumentUrl/VideoUrl) — Meta must be able to fetch it directly; a URL behind the tenant's own auth wall will fail.
  • Inbound media download failures (GET Media/{messageId} returning 400) usually mean the underlying DownloadMediaBytesAsync call to Meta failed — check whether the platform's AccessToken is still valid, since expired media links on Meta's CDN also return errors independent of the token.

No ProfileName

  • If a customer's display name is still missing after upgrading past library version 1.0.1, confirm the incoming webhook payload actually includes a contacts array with a matching wa_id — some inbound message types (e.g. system messages) may not include contact profile data at all, in which case null is the correct, expected result, not a bug.
  • Confirm the ERP's Shumoul.WhatsAppIntegration.Core/.Abstractions PackageReference versions are at 1.0.1 or later — the fix has no effect until the compiled package is actually referenced and deployed.

Delivery status not updating

  • Confirm statuses[] entries are actually present in the webhook payloads Meta is sending — some configurations only subscribe to the messages field, not message_status, in the Meta App Dashboard's webhook subscription settings.
  • Check both consumers independently: WhatsAppInboxService.ProcessWebhookStatusAsync (Inbox UI) and INotificationDeliveryReceiptService.UpsertAsync (Notification Framework) — a failure in one does not block the other, since the controller processes each item independently (see §8.8).