ADR-001: Twilio → Meta Cloud API Migration
ADR-001: Twilio → Meta Cloud API Migration
Section titled “ADR-001: Twilio → Meta Cloud API Migration”Status: accepted
Date: 2026-05-30
Deciders: @ErnestNuma
Context
Section titled “Context”ClinicFlow originally used Twilio’s WhatsApp Business API to send and receive WhatsApp messages. Twilio acted as an intermediary: the backend called Twilio’s messaging API, and Twilio relayed to Meta’s WhatsApp Cloud API. This architecture had several growing pains:
- Cost: Twilio adds a per-message markup on top of Meta’s per-conversation pricing. At scale, this margin was material.
- Latency: Every message took an extra network hop through Twilio’s infrastructure.
- Feature parity: Meta’s Cloud API exposes richer interactive messaging (native buttons, lists, templates) and simpler webhook payloads — Twilio’s abstraction layer flattened or hid some of these.
- Onboarding friction: Per-clinic WhatsApp number registration required Twilio’s Senders API flow, which had its own Meta Business Verification gate and credit-line complexities. Going direct to Meta removed one middleman’s constraints.
- Complexity: Two integration surfaces to maintain (Twilio SDK + Meta webhook format) instead of one.
Decision
Section titled “Decision”Replace Twilio’s WhatsApp Business API with direct Meta Cloud API (Graph API v22.0+).
What changed
Section titled “What changed”| Area | Before (Twilio) | After (Meta Direct) |
|---|---|---|
| Outbound messages | @twilio/messaging SDK |
metaSend.js — direct POST /<phone-number-id>/messages |
| Inbound webhooks | Twilio webhook format | Meta webhook format (GET challenge, HMAC-SHA256) |
| Number provisioning | Twilio Senders API | Direct Meta Graph API onboarding endpoints (registerNumber, requestOtp, verifyOtp) |
| Authentication | Twilio Account SID + Auth Token | Meta System User token (long-lived) |
| Interactive messages | Limited (Twilio’s abstraction) | Native Meta buttons, lists, templates |
What was removed
Section titled “What was removed”twilionpm packagesenderProvisioning.js(Twilio onboarding wrapper)- All Twilio env vars (
TWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN,TWILIO_MESSAGING_SERVICE_SID)
What was added
Section titled “What was added”metaSend.js— native Meta outbound message servicemetaProvisioning.js— Graph API onboarding wrapper- Per-clinic
senderadapter threaded through all callers (reminders, daily agenda, public endpoint, superadmin, LemonSqueezy webhook) - Meta webhook verify + HMAC-SHA256 validation
Migration path
Section titled “Migration path”All existing clinics were migrated incrementally. The sender adapter pattern meant each clinic’s WhatsApp connection could be swapped individually. Rollback was a deployment-level env revert.
Consequences
Section titled “Consequences”Positive:
- ~20% reduction in per-message cost (eliminated Twilio margin)
- Lower latency (one fewer network hop)
- Richer interactive messages available to the chatbot
- Simpler webhook payloads = less parsing code
- Direct Meta integration was necessary for future Embedded Signup and credit-line sharing (self-serve trial roadmap)
Negative:
- Meta Graph API has a steeper initial learning curve (raw HTTP vs. Twilio’s SDK)
- Meta’s Business Verification and App Review gates still apply — the migration didn’t solve that bottleneck
- Existing Twilio-dependent tooling (monitoring, logging) needed updates
Risks:
- Meta API versioning (breaking changes on deprecation) — mitigated by pinning
META_GRAPH_API_VERSIONin env - System User tokens expire (2h default) — mitigated by using long-lived tokens with refresh
Compliance
Section titled “Compliance”metaSend.jsis the only outbound path; grep fortwilioinbackend/src/should return zero hits- Tests mock the Graph API HTTP layer, not Twilio SDK
- CI pipeline checks for accidental
require('twilio')imports
- WhatsApp Meta API Notes — live-verified API surface
- Implementation plan: 2026-05-30-whatsapp-meta-direct-migration.md
- Supersedes: Twilio-based approach (deprecated)