Meta WhatsApp Non-Template Pricing — Implementation Impact Analysis
Sources:
- Meta dev docs: https://developers.facebook.com/documentation/business-messaging/whatsapp/pricing/non-template-messages
- Prior art (July 2025 PMP migration): [PRD] Meta Pricing Adjustment - Per Message Pricing — https://jurnal.atlassian.net/wiki/spaces/QON/pages/49741725983 (Jira: BIF-4331, BIF-4332, BIF-4626, BIF-4616)
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_model—PMP(per-message) orCBP(conversation-based). All clients are onPMPsince 1 July 2025.pricing.type— the billability flag:regular→ billablefree_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. Noteservice= "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=truededucts once per unique conversation id at the category price + margin;billable=falseis free.pricing_model = PMP→ new flow:type=regulardeducts once per unique message id at the category price + margin;type=free_customer_service/free_entry_pointis 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/categoryoff the Meta webhook and deducting (per-conversation for CBP, per-message-id for PMP) — lives inhub_core, not in qontak-billing (see 3.2).hub_corealso writespricing_model/pricing_typeontowa_conversation_logsin the shared billing DB. - Under today's rules a
servicemessage inside the CSW istype=free_customer_service, so the PMP flow skips deduction.
Changes needed:
- Verify the PMP
type=regularbranch bills a message whencategory=service— i.e. thatserviceisn't special-cased to "free" independently of thetypeflag. If it is, remove that special-case so aregularservice message deducts at theservicecategory price like any otherregularmessage. - No new per-message dedup needed — the PMP flow already keys on unique message id.
- Blocker: get
hub_corechecked 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 viawa_uniq_conv_id_logs) and carries nopricing_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 recognizeservice(maps toConversationType=UI, ConversationCategory=service) and resolve its price viabillingService.GetWhatsappPricing(...)— so the price-lookup path forserviceexists.v2_wa_conversation_prices(country, conversation_type, conversation_category, cost)is the price catalog, keyed by category includingservice.
Changes needed:
- Confirm/seed the
servicecategory rate inv2_wa_conversation_pricesper 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-billedregularservice 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_logsschema (mirrored here) already includespricing_model,pricing_type,conversation_category, andmessage_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/IsMCCLogsExistsfilter onis_auto_deduct = TRUEand a fixedcredited_tolist. 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
servicerows (now carrying a real deduction) appear. Likely no schema change —conversation_category,pricing_type, andmessage_idare 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
- hub_core: get repo access; verify the PMP
type=regularbranch billscategory=service(remove any "service is always free" special-case). - qontak-billing: confirm/seed the
servicerate inv2_wa_conversation_prices(per country; margin = 0 at launch). - report-worker: verify/fix the MCC export filter so billable service rows appear.
- 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.