5. Runtime Flows
Every flow below is verified against the current, post–Phase 1 source code — no invented steps.
5.1 Send Template Message (Communication Module / Business Document)
5.2 Send OTP
OTP codes are never logged — TenantNotificationLog stores the literal string "Template:{templateName}" as
the message body, never the rendered parameter values.
5.3 Send Activation / Welcome Message
Identical shape to §5.2, triggered by TenantSubscriptionActivatedEvent →
SendSubscriptionActivatedWhatsAppHandler → WhatsAppActivationMessageService.SendTenantActivationMessageAsync,
using the welcome_activation_ar/welcome_activation_en templates with two body parameters (customer name,
organization name).
5.4 Send Invoice (Business Document)
Same code path as §5.1 — an invoice
send is CommunicationService.SendDocumentAsync with Channel = WhatsApp and DocumentType = "Invoice". No
separate implementation exists; it is documented here only to explicitly confirm coverage of this flow.
5.5 Receive Webhook
The controller always returns 200 OK for a structurally valid, signature-valid request regardless of
per-item processing failures — Meta retries on non-2xx, so swallowing per-item errors after acknowledging
receipt avoids duplicate-delivery storms.
5.6 Verify Webhook
5.7 Reply From Inbox
Free-text replies (SendReply) are only permitted inside Meta's 24-hour customer-service window
(CanSendFreeText on WhatsAppConversationDto); outside that window, only an approved template reply
(SendTemplateReply) is allowed — this is a Meta platform rule, not a Shumoul business rule.
5.8 Media Upload / Send / Download
Outbound ("upload"/"send"): this codebase has no separate Meta media-upload endpoint call — outbound
media is attached to a template component as a public URL (ImageUrl/DocumentUrl/VideoUrl on
WhatsAppTemplateComponentParameterDto). "Upload" and "send" are therefore the same flow as
§5.1/§5.7.
Inbound (download):
The Meta CDN URL is never exposed to the frontend — the backend always proxies the bytes through
GetMediaInfoAsync/DownloadMediaAsync.
5.9 Delivery Status Update
Two independent consumers of the same webhook status event (see §5.5):
WhatsAppInboxService.ProcessWebhookStatusAsync— updatesWhatsAppMessage.MessageStatusfor the Inbox UI.INotificationDeliveryReceiptService.UpsertAsync— feeds the Notification Framework's generic delivery-receipt/retry pipeline viaWhatsAppMetaReceiptMapper's status-string mapping.
5.10 Error Handling
- Meta API errors (non-2xx or an
"error"object in the response body): parsed identically byWhatsAppCloudApiClientfor every send path, since Phase 1 made it the single client. The(Success, MessageId, ErrorMessage)/(Success, ExternalId, ErrorMessage)tuple shape is consistent across all callers. - Notification Framework retry/dead-letter path:
NotificationFailureClassifier's Meta-error-code heuristics decide whether a failed dispatch is retryable — unrelated to and untouched by the WhatsApp library itself. - Webhook processing errors: always per-item try/catch; the controller acknowledges Meta with
200 OKregardless of individual parse/persist failures (see §5.5).
5.11 Tenant Credentials
Platform-wide credentials (WhatsAppCloudApiOptions) and tenant-scoped credentials
(WhatsAppOperationalSettings) are two entirely separate configuration surfaces — see
§4 Configuration. Both
ultimately feed the same WhatsAppCloudApiClient class, just with different WhatsAppCloudApiOptions
instances constructed at the point of use.
