Skip to content

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


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:

  1. Cost: Twilio adds a per-message markup on top of Meta’s per-conversation pricing. At scale, this margin was material.
  2. Latency: Every message took an extra network hop through Twilio’s infrastructure.
  3. 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.
  4. 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.
  5. Complexity: Two integration surfaces to maintain (Twilio SDK + Meta webhook format) instead of one.

Replace Twilio’s WhatsApp Business API with direct Meta Cloud API (Graph API v22.0+).

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
  • twilio npm package
  • senderProvisioning.js (Twilio onboarding wrapper)
  • All Twilio env vars (TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_MESSAGING_SERVICE_SID)
  • metaSend.js — native Meta outbound message service
  • metaProvisioning.js — Graph API onboarding wrapper
  • Per-clinic sender adapter threaded through all callers (reminders, daily agenda, public endpoint, superadmin, LemonSqueezy webhook)
  • Meta webhook verify + HMAC-SHA256 validation

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.

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_VERSION in env
  • System User tokens expire (2h default) — mitigated by using long-lived tokens with refresh
  • metaSend.js is the only outbound path; grep for twilio in backend/src/ should return zero hits
  • Tests mock the Graph API HTTP layer, not Twilio SDK
  • CI pipeline checks for accidental require('twilio') imports