Problem / Motivation
WhatsApp (2B+ users globally) is the dominant messaging platform for customer engagement in APAC, LATAM, Europe, and Africa. EDDI's conversational agents can power customer support, sales qualification, and onboarding workflows via WhatsApp Business.
Note: WhatsApp Business API is primarily 1:1 (business-to-customer). A Groups API exists for Official Business Accounts (max 8 participants), but it is a restricted, premium feature - not the standard use case. The initial adapter should support AGENT targets only. GROUP target support via the Groups API can be a future enhancement.
Key WhatsApp constraints:
- Primarily 1:1 - Standard use case is business-to-customer direct messaging
- Groups API - Exists but requires Official Business Account (OBA) status, max 8 participants
- 24-hour messaging window - Businesses can only send free-form messages within 24 hours of the customer's last message. Outside this window, only pre-approved message templates are allowed.
- Cloud API (Meta-hosted) - Modern REST API via Meta's Graph API
- Webhook with HMAC-SHA256 - Same algorithm as Slack (different header:
X-Hub-Signature-256)
- Meta app verification - Production use requires Meta business verification
Architecture Principle
No platform SDKs. All adapters use java.net.http.HttpClient + Jackson for raw HTTP/JSON - no Twilio, no third-party WhatsApp libraries. Every messaging platform's API is just REST under the hood. Raw HTTP keeps the dependency tree lean, the single-JAR deployment simple, and gives full control over retry logic, error handling, and message formatting. See the existing SlackWebApiClient (245 lines, zero external dependencies) as the reference pattern.
Proposed Solution
Create ai.labs.eddi.integrations.whatsapp package:
| Class |
Responsibility |
Slack Equivalent |
RestWhatsAppWebhook |
JAX-RS endpoint with @GET for Meta's webhook verification challenge (hub.verify_token + hub.challenge echo) and @POST for event payloads. |
RestSlackWebhook |
WhatsAppSignatureVerifier |
HMAC-SHA256 verification using Meta App Secret. Same algorithm as Slack but header is X-Hub-Signature-256 with sha256= prefix. |
SlackSignatureVerifier |
WhatsAppEventHandler |
Routes WhatsApp messages to EDDI via ChannelTargetRouter. Only supports AGENT targets - rejects GROUP targets with a user-facing error. Tracks message timestamps for 24-hour window enforcement. |
SlackEventHandler |
WhatsAppApiClient |
Sends messages via Meta Graph API (POST /v21.0/{phone-number-id}/messages). Supports text, interactive buttons (for HITL approvals), and message templates (for out-of-window). Marks messages as read. |
SlackWebApiClient |
WhatsAppDeliveryException |
Retryable delivery failure. |
SlackDeliveryException |
Note: No WhatsAppGroupDiscussionListener - GROUP targets are not supported in the initial adapter. The event handler should return a clear error message if a WhatsApp message routes to a GROUP target: "Multi-agent discussions are not available on WhatsApp. This target requires a platform that supports group conversations (Slack, Teams, Discord)."
Configuration Model
{
"name": "Customer Support WhatsApp",
"channelType": "whatsapp",
"platformConfig": {
"phoneNumberId": "123456789",
"accessToken": "${vault:whatsapp-access-token}",
"appSecret": "${vault:whatsapp-app-secret}",
"verifyToken": "${vault:whatsapp-verify-token}",
"businessAccountId": "987654321"
},
"defaultTargetName": "support",
"targets": [...]
}
Key Design Decisions
- AGENT targets only (initial scope) - GROUP targets are not supported in the initial adapter. WhatsApp's Groups API exists but requires OBA status, has a max of 8 participants, and is a fundamentally different workflow from the core 1:1 business messaging use case. GROUP support via the Groups API is a natural Phase 2 enhancement.
- Cloud API only - Use Meta's Cloud API. No on-premise BSP partnership required.
- Webhook verification (GET challenge) - Meta sends
GET with hub.mode=subscribe, hub.verify_token, hub.challenge. Echo hub.challenge on token match. This is the WhatsApp equivalent of Slack's url_verification.
- 24-hour window tracking - Track customer's last message timestamp per conversation. Within 24 hours: send free-form text. Outside: send pre-approved message template OR log a warning and skip. Do NOT silently drop messages.
- Interactive buttons - Use WhatsApp's Interactive Message buttons (up to 3) for HITL approval flows.
- Message formatting - WhatsApp supports
*bold*, _italic_, ~strikethrough~, `monospace`. Very close to Slack's mrkdwn - can largely reuse SlackWebApiClient.convertMarkdownToSlackMrkdwn() with minor adjustments.
- Read receipts - Mark messages as read via the API for better UX feedback.
- Loop prevention - WhatsApp Cloud API only delivers customer messages to the webhook (not the bot's own messages). Infinite loops are impossible at the API level. Still implement dedup by
messages[].id for webhook retries.
Deliverables
Alternatives Considered
- Twilio as intermediary - Adds cost and dependency. Direct Cloud API integration is cleaner and consistent with the no-SDK principle.
- On-Premise API via BSP - More complex setup, requires partnership with a Business Solution Provider. Cloud API is the modern, recommended approach.
Additional Context
Acceptance Criteria
Problem / Motivation
WhatsApp (2B+ users globally) is the dominant messaging platform for customer engagement in APAC, LATAM, Europe, and Africa. EDDI's conversational agents can power customer support, sales qualification, and onboarding workflows via WhatsApp Business.
Key WhatsApp constraints:
X-Hub-Signature-256)Architecture Principle
Proposed Solution
Create
ai.labs.eddi.integrations.whatsapppackage:RestWhatsAppWebhook@GETfor Meta's webhook verification challenge (hub.verify_token+hub.challengeecho) and@POSTfor event payloads.RestSlackWebhookWhatsAppSignatureVerifierX-Hub-Signature-256withsha256=prefix.SlackSignatureVerifierWhatsAppEventHandlerChannelTargetRouter. Only supports AGENT targets - rejects GROUP targets with a user-facing error. Tracks message timestamps for 24-hour window enforcement.SlackEventHandlerWhatsAppApiClientPOST /v21.0/{phone-number-id}/messages). Supports text, interactive buttons (for HITL approvals), and message templates (for out-of-window). Marks messages as read.SlackWebApiClientWhatsAppDeliveryExceptionSlackDeliveryExceptionNote: No
WhatsAppGroupDiscussionListener- GROUP targets are not supported in the initial adapter. The event handler should return a clear error message if a WhatsApp message routes to a GROUP target: "Multi-agent discussions are not available on WhatsApp. This target requires a platform that supports group conversations (Slack, Teams, Discord)."Configuration Model
{ "name": "Customer Support WhatsApp", "channelType": "whatsapp", "platformConfig": { "phoneNumberId": "123456789", "accessToken": "${vault:whatsapp-access-token}", "appSecret": "${vault:whatsapp-app-secret}", "verifyToken": "${vault:whatsapp-verify-token}", "businessAccountId": "987654321" }, "defaultTargetName": "support", "targets": [...] }Key Design Decisions
GETwithhub.mode=subscribe,hub.verify_token,hub.challenge. Echohub.challengeon token match. This is the WhatsApp equivalent of Slack'surl_verification.*bold*,_italic_,~strikethrough~,`monospace`. Very close to Slack's mrkdwn - can largely reuseSlackWebApiClient.convertMarkdownToSlackMrkdwn()with minor adjustments.messages[].idfor webhook retries.Deliverables
ai.labs.eddi.integrations.whatsapp(no GroupDiscussionListener)"whatsapp"toREGISTERED_CHANNEL_TYPESdocs/whatsapp-integration.mdAlternatives Considered
Additional Context
Acceptance Criteria
"whatsapp"added toREGISTERED_CHANNEL_TYPESinRestChannelIntegrationStore./mvnw testpasses with zero failuresdocs/whatsapp-integration.mdexists (followingdocs/slack-integration.mdstructure)