Appearance
ADR-0029 · WhatsApp communication platform via Meta Cloud API, direct
Status: 🟡 Proposed — direction set by the owner 2026-08-21 (Meta app "QR Setu" already activated; explicit instruction to avoid third-party WhatsApp providers), recorded here with its costs stated · Depends on: ADR-0008 (channel split), ADR-0027 (outbox enqueue discipline — the drain worker is shared scope), ADR-0021 (merchant campaign gating), ADR-0028 (admin vs org trust boundaries) · Amends: ADR-0008 (WhatsApp becomes the primary customer-facing notification channel; email keeps OTP and account mail)
Context
QRSETU needs off-app communication for both sides of every transaction: buyers (mostly anonymous, no app, phone number known — orders.buyer_phone is NOT NULL) and merchants (signup, orders, payouts, proactive nudges). The owner's direction is WhatsApp-first for India, explicitly avoiding SMS/DLT infrastructure at this stage, and integrating Meta's official APIs directly rather than a third-party WhatsApp platform (WATI, MSG91, or any BSP).
The Meta capability surface was verified against live documentation on 2026-08-21 — the full matrix, with citations and per-claim verification tags, is the portal's Meta API assessment. The facts that decide this ADR:
- A business messaging from its own WABA needs no App Review (Standard Access, direct developer) — Phase 1 is buildable immediately with a System User token.
- Send, receive, template lifecycle, quality/limit signals, and usage/cost analytics (
pricing_analytics) are all first-party APIs with webhooks. - The two things Meta does NOT offer are billing automation (no API for payment methods, invoices, or any top-up concept) and prepaid balances — which are precisely the conveniences BSPs resell.
- Meta enforces quality economically (per-user marketing caps live in India, template pausing, tier drops), which any architecture must treat as policy, not transience.
Options considered
A — Meta Cloud API, direct · [recommended, and the owner's stated direction]
- For: no per-message BSP margin on top of Meta's rates; one system of record; first-party webhooks and analytics; no App Review for our own WABA; full control of the template registry and consent model; the future Tech Provider path stays open.
- Against / the costs, stated: we own webhook infrastructure, retry, template ops and quality management ourselves; billing is a manual Billing Hub runbook (true on every path — BSPs merely hide it); Meta's doc churn is ours to track.
B — BSP (WATI, MSG91, Interakt, …)
- For: faster first send; prepaid wallets and invoicing UI; support.
- Against: per-message margin forever; a second system of record for templates/consent; vendor lock-in on exactly the layer QRSETU intends to expose to its own tenants later; contradicts the owner's explicit instruction.
C — Hybrid (direct for transactional, BSP for marketing)
- Against: two template registries, two consent stores, two webhook shapes — the duplicate-source-of-truth class (QRS-249) bought deliberately. Rejected.
D — SMS/DLT first
- Against: DLT registration overhead, worse medium for the audience, and the owner has explicitly deferred it. WhatsApp does not preclude adding SMS later behind the same channel-agnostic envelope.
Decision (proposed)
- Direct Meta integration, one Meta app ("QR Setu"), one platform WABA, INR-billed from creation (India INR migration is mandatory by 2026-12-31).
- A channel-agnostic envelope with a single dispatcher choke point — features enqueue (message row + outbox
comm.send, same transaction as the business write); only the dispatcher talks to Meta; only the webhook EF listens. Design: architecture · data model. - QRSETU is the single sender of record in R1. Per-tenant WABAs (Embedded Signup, Tech Provider, Advanced Access) are a later programme; the schema carries
workspace_id/owner_workspace_idfrom the first migration so that programme changes rows, not shape. ⚠ ADR-0030 now decides that programme in detail — including that a customer's own branding and QRSETU-sold prepaid credits are mutually exclusive without Solution Partner status, and that presenting a customer's name on a number inside QRSETU's own WABA is prohibited rather than a shortcut. - Consent fails closed for marketing, keyed by phone (anonymous-first); transactional consent is the transaction itself, per Meta's opt-in policy and DPDP.
- The Admin Communication Hub is a platform-admin surface (module contract); it is a design REQUEST to Claude Design (no admin-panel screen exists), and its access is gated on the
is_admin()decision (QRS-803), which this ADR does not preempt. - Phasing: P1 transactional utility on the money path → P2 template/usage operations → P3 marketing campaigns → P3+ tenant senders and in-chat payments. The in-chat-payments option is explicitly blocked on verifying Razorpay Route/split compatibility with WhatsApp-originated payments (undocumented at Meta) before any design.
Consequences
- Two open prerequisites are inherited, not created: the outbox has no drain worker (shared with ADR-0025/0027), and the outbox
topicCHECK needscomm.sendadded by migration. - ADR-0008 is amended, not superseded: email keeps OTP (Supabase native) and account mail; ZeptoMail's transactional/promotional split maps 1:1 onto UTILITY/MARKETING and shares the consent discipline.
- Ops burden accepted knowingly: billing runbook (no API), template quality management, webhook health monitoring — the watchdog-workflow pattern applies to a channel that is otherwise its own only witness.
- A future BSP retreat stays cheap: everything provider-specific lives behind the dispatcher and the
whatsapp_*registries; the envelope, consent ledger and admin surfaces are provider-neutral.