Skip to main content

Meta WhatsApp Non-Template Pricing — Implementation Impact Analysis

Sources:

1. Summary of the Meta Change (as it applies to Qontak)

Meta is making previously-free service (non-template) messages billable per message from October 1, 2026, at rates matching the utility/authentication categories.

Scope note — Meta Business Agent is out of scope. Meta's announcement also introduces a "Meta Business Agent" (Meta's own hosted AI) billed per-token from Aug 1, 2026. Qontak does not use Meta Business Agent, so the per-token billing dimension is not applicable and is excluded from this analysis. The only change relevant to Qontak is the per-message billing of service messages.

2. This is a continuation of the July 2025 PMP work — not a new billing model

Qontak already migrated to Meta's Per-Message Pricing (PMP) model in July 2025 (PRD above, implemented under BIF-4331). That work built exactly the machinery this change rides on. Recapping the webhook semantics Qontak already handles:

  • pricing_modelPMP (per-message) or CBP (conversation-based). All clients are on PMP since 1 July 2025.
  • pricing.type — the billability flag:
    • regularbillable
    • free_customer_service → free (utility template or non-template/service message sent inside the 24h customer service window)
    • free_entry_point → free (free-entry-point conversation)
  • category — the rate applied; unchanged under PMP. Note service = "a non-template message" (per Meta's own definition, quoted in the July PRD).

The BIF-4331 PMP deduction flow already implemented:

  • pricing_model = CBP → legacy flow: billable=true deducts once per unique conversation id at the category price + margin; billable=false is free.
  • pricing_model = PMP → new flow: type=regular deducts once per unique message id at the category price + margin; type=free_customer_service / free_entry_point is free.

What actually changes on 1 Oct 2026: today a category=service message inside the customer service window arrives as type=free_customer_service → the PMP flow correctly treats it as free. After the switch, Meta will send those same service messages as type=regular → they become billable. The existing PMP type=regular path already knows how to bill a regular message per message-id at its category price. So the change is primarily: (a) ensure a service category price exists, and (b) verify the PMP flow bills category=service when type=regular (it may have been written assuming service is always free).

3. Repo-by-Repo Findings and Required Changes

3.1 hub_core (Ruby) — the PMP deduction engine — highest priority, and where the real logic lives

Current state (inferred; repo not checked out):

  • The CBP-vs-PMP branching from BIF-4331 — reading pricing_model / pricing.type / category off the Meta webhook and deducting (per-conversation for CBP, per-message-id for PMP) — lives in hub_core, not in qontak-billing (see 3.2). hub_core also writes pricing_model / pricing_type onto wa_conversation_logs in the shared billing DB.
  • Under today's rules a service message inside the CSW is type=free_customer_service, so the PMP flow skips deduction.

Changes needed:

  • Verify the PMP type=regular branch bills a message when category=service — i.e. that service isn't special-cased to "free" independently of the type flag. If it is, remove that special-case so a regular service message deducts at the service category price like any other regular message.
  • No new per-message dedup needed — the PMP flow already keys on unique message id.
  • Blocker: get hub_core checked out (Gemfile path ../hub_core) to confirm the above; it is the one component that must be read directly.

3.2 qontak-billing (Go) — price catalog + (legacy) conversation-based deduction

Current state:

  • Its visible WhatsApp deduction endpoint (internal/app/usecase/package/whatsapp_deduction.go) is conversation-id-keyed (dedups via wa_uniq_conv_id_logs) and carries no pricing_model / type / message_id — i.e. it is the CBP-era / conversation-based path, not the PMP per-message engine (which is in hub_core). It contains no PMP, free_customer_service, or message-id logic.
  • mapPricing() does already recognize service (maps to ConversationType=UI, ConversationCategory=service) and resolve its price via billingService.GetWhatsappPricing(...) — so the price-lookup path for service exists.
  • v2_wa_conversation_prices(country, conversation_type, conversation_category, cost) is the price catalog, keyed by category including service.

Changes needed:

  • Confirm/seed the service category rate in v2_wa_conversation_prices per country to Meta's Oct 2026 per-message rate (Qontak margin = 0 at launch, per §5 — so the client charge equals Meta's rate). Today it is likely 0 / absent because service has always been free. This is the single most important data change — without a price, even a correctly-billed regular service message deducts nothing.
  • Confirm which service the price lookup (GetWhatsappPricing) actually reads from so the seeded rate is the one hub_core's PMP flow uses.

3.3 report-worker (Go) — billing exports/reporting

Current state:

  • wa_conversation_logs schema (mirrored here) already includes pricing_model, pricing_type, conversation_category, and message_id — the July PRD already added a "Message ID" column to the WA Convo report (view BIF-4332, client download BIF-4626, Modpanel MCC download BIF-4616). So message-level reporting already exists.
  • FetchMCCLogsExport / IsMCCLogsExists filter on is_auto_deduct = TRUE and a fixed credited_to list. If service rows were historically written as non-deductible (free), they would be excluded even after becoming billable.

Changes needed:

  • Verify/fix the MCC export filter so newly-billable service rows (now carrying a real deduction) appear. Likely no schema change — conversation_category, pricing_type, and message_id are already columns.

3.4 hub / hub-chat (Nuxt) — customer-facing usage/billing UI — lowest priority

  • hub: components/finances/package_usage/ReportingMcc.vue; hub-chat: features/subscriptions/usages/TableComponentWhatsappBalance.vue.
  • The July PMP work already surfaced per-message data (Message ID column) here. Once service is billed, confirm service-category rows render correctly in the usage report and totals — a display/label check, not new UI.

4. Suggested Sequencing

  1. hub_core: get repo access; verify the PMP type=regular branch bills category=service (remove any "service is always free" special-case).
  2. qontak-billing: confirm/seed the service rate in v2_wa_conversation_prices (per country; margin = 0 at launch).
  3. report-worker: verify/fix the MCC export filter so billable service rows appear.
  4. hub / hub-chat: confirm service usage renders in customer-facing reports.

5. Product Question — RESOLVED (2026-07-22)

Will Qontak absorb, pass through 1:1, or bundle the new Meta service-message fee (i.e. what margin to add on top of Meta's rate)? Resolved: pass through 1:1 at cost — a 0 Qontak margin at launch. The service base price in v2_wa_conversation_prices is Meta's per-country rate, and the service margin is 0 (new CIDs get 0 at account creation; existing CIDs are backfilled to 0 on release). Unlike the July 2025 "category price + margin" convention, service launches with a 0 margin — the margin field still exists and is configurable, so a markup can be introduced later. See the PRD (§14 Key Decisions) and the initiative README.

6. Known Gap

The hub_core repository (Gemfile path ../hub_core) is not checked out in this environment. It holds the PMP deduction engine (the BIF-4331 CBP/PMP branching) that will actually decide whether a regular service message gets billed — the one component that must be read directly to close this analysis.