Skip to main content

Qontak | Billing & Subscription | One CID Multiple WABA — Phase 1: Shared Balance Pool

Product Requirements Document · NEW PRD v1.0


HEADER BLOCK

FieldValue
PMAddo Hernando, Ega Javier Harwenda
PRD Version1.0
StatusDRAFT
PRD TypeNEW
EpicBIF-6428
SquadBifrost
RFC LinkTBD — pending PRD approval
Figma MasterFigma — Subscription
AnchorNo — standalone, single-squad
Labelsepic:billing-platform | module:billing-subscription | feature:one-cid-multiple-waba
Last Updated2026-06-29
SourcePRD Implementation of One CID Multiple Waba ID - Billing V3

Table of Contents


1. One-liner + Problem

One-liner: Enable Billing V3 companies with multiple WABA IDs to share a single aggregated WhatsApp balance pool, eliminating per-WABA balance confusion under Meta's Coexistence model.

Problem: Companies using Meta's new Coexistence model can register multiple WhatsApp Business Account IDs (WABA IDs) under a single company, but Qontak's current billing system tracks and displays balance at the individual WABA level — each WABA appears to have an independent credit pool. This forces Company Admins and Qontak Finance teams to manually reconcile per-WABA balances, making it impossible to see total credit availability at a glance, and creating a high risk of unexpected service interruptions when one WABA exhausts its individual allocation while others still have credit. The Billing V3 component-based quota architecture (organization_package_component_initials, _additionals, _postpaid_limits) was designed for a shared-pool model but is not yet surfaced to the user; without the aggregation layer, the multi-WABA value proposition of Meta Coexistence is blocked for Qontak clients.


2. Target Users + Persona Context

PersonaRoleGoalPainWorkaround
Primary — Company Admin (V3)Owner or Admin managing a Billing V3 account with ≥2 WABA IDs registered under Meta CoexistenceView a single consolidated WhatsApp balance and understand how it is consumed across all WABA IDs without doing manual mathMust add up individual per-WABA balances to estimate total credit; balance resets and deductions feel unpredictable because the pool logic is opaque; risk of unexpected service blockage when one WABA exhausts quota while the company overall still has creditChecks each WABA's balance separately in Package Usage page; maintains a side spreadsheet to sum totals; contacts Qontak Finance support to confirm available credits before large campaigns
Secondary — Qontak Finance TeamInternal Finance/Activation ops user managing credit top-ups, postpaid limits, and contract renewals for V3 clientsConfigure and monitor a single aggregated WhatsApp credit pool per company — not per WABA — so that limit and top-up operations are performed once per companyMust mentally aggregate across WABAs when setting postpaid limits or confirming a top-up amount; reconciliation between Metabase CID/WABA dashboards and per-WABA billing records is slow and error-proneUses internal Metabase dashboards (see check_cid_and_waba_id and check_billing_version in moderator-be) to cross-reference CID vs WABA usage; manually aggregates before making top-up decisions

3. Non-Goals

  1. Balance isolation per WABA — each WABA ID will NOT have its own independent spending cap or sub-quota in this phase; all WABAs share the single company pool with no per-WABA allocation rules.
  2. Billing V1 and V2 clients — the shared pool model applies only to billing_version = "3.0.0" organizations; V1/V2 behavior (per-WABA wa_credit/wa_balance fields on WhatsappPackage) is entirely unchanged.
  3. Mobile app changes — this feature is web-only; no changes to the Qontak mobile app in this phase.
  4. Real-time balance push updates — balance displayed in the UI updates on page refresh or explicit reload, not via WebSocket live push.
  5. Per-WABA spending limit configuration — Admins cannot set individual spending caps per WABA ID in this phase; that requires a future allocation-rules initiative.
  6. Multi-currency support — balance is denominated in a single currency; multi-currency conversion is out of scope.
  7. Historical per-WABA usage data migration — existing WaConversationLog records are preserved as-is; only the balance aggregation model changes for new deductions going forward.
  8. Automatic top-up trigger — low balance notifications alert the user, but no auto-recharge is implemented in this phase.

Scope Changes

Engineering surfaces this PRD touches (controlled vocab). Kept in sync with the scope_changes frontmatter above.

  • Backendqontak-billing: aggregate balance query across all WhatsappPackage rows linked to the same organization_package_id; 3-tier deduction logic using organization_package_component_initialsadditionalspostpaid_limits; monthly WABI reset job (is_initial_monthly_reset = true on BillingComponent); carry-over on renewal (is_carry_over_contract = true); low balance notification events; billing_report_show_waba_id preference gates waba_id column in WaConversationLog report queries. hub-service: /api/core/v1/billings/info returns aggregated balance for V3 orgs; GET /mcc_logs adds optional waba_id filter; GET /download_broadcast_deduction includes waba_id column when billing_report_show_waba_id is enabled.
  • Frontendhub-chat: shared balance tooltip on Package Usage page (/subscriptions/packages); waba_id column in Broadcast deduction table and Conversation/MCC log table; waba_id field in CSV export for both report types; low balance banner driven by billingM1Version flag.
  • DataWhatsappPackage.organization_package_id FK already added (migration 20260113000000); billing_report_show_waba_id preference entry in OrganizationPreference; monthly WABI reset cron job configuration in qontak-billing scheduler.

4. Constraints

FieldValue
PlatformWeb only — no mobile changes in this phase.
PerformanceAggregated balance query ≤ 500ms p95. Package Usage page (with tooltip) loads ≤ 2s. Report table with waba_id column renders ≤ 2s for up to 500 rows. CSV export completes ≤ 10s for up to 10,000 rows.
Data limitsMax WABA IDs per organization: no Qontak-imposed hard cap in this phase (follows Meta Coexistence limits). Balance values displayed to 4 decimal places (matching existing wa_balance precision in OrganizationPackage).
Plan scopeApplies exclusively to organizations with billing_version = "3.0.0". V1 (1.0.0) and V2 (2.0.0) organizations are entirely unaffected.
Feature flagbilling_report_show_waba_id | default: OFF — enabled per CID to surface waba_id column in Broadcast and Conversation reports. Shared balance pool and deduction logic are active for ALL V3 orgs without a separate flag (tied to billing_version check).
Read/writeCompany Admin: read aggregated balance, view tooltip, view waba_id in reports, configure postpaid limit. Qontak Finance Team: configure postpaid limit, add WAB-Additional credits via Modpanel. CS Agent: read-only access to waba_id column in reports they have permission to view.

4.1. Data Lifecycle

Artifact TypeRetention PeriodCleanup TriggerUser-Visible Effect
WaConversationLog records (with waba_id)90 days from created_atExisting nightly TTL cleanup job in qontak-billingNone — logs older than 90 days no longer appear in report queries
WhatsappUsageComparison records (CID vs Meta reconciliation)90 days from created_atNightly cleanup jobNone — internal reconciliation data only
Component quota snapshots (organization_package_component_initials at reset)Duration of contract + 6 monthsContract lifecycle cleanupNone — audit trail only
Low balance notification event log30 daysNightly event log cleanupNone — internal observability only

5. Feature Changes

Change ID: CHG-001

FieldDetail
Change TypeModified component
PagePackage Usage — Broadcast deduction report table (path TBD by engineering; currently at /subscriptions/packages or /billing/usage)
Page IntentAdmin or Finance user reviews broadcast message usage and associated balance deductions per period.
Before• Broadcast deduction table has no waba_id column. Usage rows are not identifiable by which WABA sent them.
After• A waba_id column is added to the broadcast deduction table when billing_report_show_waba_id preference is ON for the CID. • CSV export includes the waba_id field on every row when the column is visible.
ElementBeforeAfter
Broadcast deduction table columnsdate, type, messages, deducted_amount (approximate)Same + waba_id column (gated by billing_report_show_waba_id)
CSV export fieldsSame columns as tableSame + waba_id field when preference is ON

Figma: Figma — Subscription (frame-level link TBD)


Change ID: CHG-002

FieldDetail
Change TypeModified component
PagePackage Usage — Conversation / MCC Logs table (/subscriptions/packages or equivalent)
Page IntentAdmin or Finance user reviews per-conversation billing deductions to reconcile usage and understand credit consumption.
Before• MCC log table has no waba_id column. Conversation records cannot be attributed to a specific WABA ID at a glance. • No per-WABA filter is available on the conversation log view.
After• A waba_id column is added to the MCC log table when billing_report_show_waba_id preference is ON. • A waba_id filter dropdown is added to allow filtering conversation logs by a specific WABA ID. • CSV export includes waba_id on every row when the column is visible.
ElementBeforeAfter
MCC log table columnsdate, conversation_id, type, credited_to, amount (approximate)Same + waba_id column (gated by preference)
Filter optionsNo WABA filterwaba_id filter dropdown added
CSV exportSame columns as tableSame + waba_id field when preference ON

Figma: Figma — Subscription (frame-level link TBD)


Change ID: CHG-003

FieldDetail
Change TypeModified component
PagePackage Usage page — WhatsApp balance display section
Page IntentAdmin views current WhatsApp credit availability to decide on top-ups or campaign scheduling.
Before• For V3 orgs with multiple WABAs, balance is displayed at the individual WhatsappPackage level (per WABA). Each WABA appears to have a separate pool.
After• For V3 orgs (billing_version = "3.0.0"), balance is displayed as a single aggregated company-level pool. The WABI (initial), WAB-Additional, and Postpaid buckets each show a single consolidated figure summed across all linked WABA IDs.
ElementBeforeAfter
WABI balance displayPer WABA (e.g., WABA-1: 3,000 / WABA-2: 2,000)Single company total (e.g., 5,000)
WAB-Additional displayPer WABASingle company total
Postpaid Limit displayPer WABASingle company total

Figma: Figma — Subscription (frame-level link TBD)


6. New Features

Feature: Shared Balance Tooltip

FieldDetail
URL/subscriptions/packages (Package Usage page — existing page)
AccessCompany Admin (all roles who can view the Package Usage page)

Component Tree:

ComponentParentPurpose
SharedBalanceTooltipWhatsApp balance section headerTooltip trigger icon displayed next to the WhatsApp balance heading; visible only when billing_version = "3.0.0"
TooltipContentSharedBalanceTooltipPopover content explaining that the displayed balance is shared across all WABA IDs registered under the company

UI States:

StateDescription
HiddenTooltip icon not rendered for non-V3 organizations (billing_version ≠ "3.0.0")
IdleInfo icon (ⓘ) displayed next to "WhatsApp Balance" heading
ActiveOn hover/click: tooltip popover appears with text: "This balance is shared across all your WhatsApp numbers (WABA IDs). Any message sent by any connected WABA ID draws from this single pool."
ErrorN/A — tooltip content is static, no API dependency

Figma: Figma — Subscription (Low Balance example) (frame-level link TBD)


7. API & Webhook Behavior

#BehaviorEntity AffectedTriggered ByExpected BehaviorFailure Behavior
1Aggregate balance queryOrganizationPackage + linked WhatsappPackage rows (via organization_package_id FK)Frontend loads Package Usage page or calls /api/core/v1/billings/infoFor billing_version = "3.0.0" orgs: sum wa_balance_initial, wa_balance, and wa_credit across all WhatsappPackage rows sharing the same organization_package_id. Return a single aggregated WABI, WAB-Additional, and Postpaid Limit value. V1/V2 orgs return per-WABA values as before — no change.If query times out (> 500ms p95): return last-known cached value and log billing_aggregate_balance_timeout. If no WhatsappPackage rows exist for the org: return zeros.
2Multi-bucket deduction hierarchyorganization_package_component_initialsorganization_package_component_additionalsorganization_package_component_postpaid_limits (V3 quota tables)Outbound message sent by any WABA ID linked to the OrganizationPackageDeduct from WABI (initial quota) first. When WABI is exhausted (remaining_quota = 0), continue deduction from WAB-Additional. When WAB-Additional is exhausted, deduct from Postpaid Limit. Record credited_to in WaConversationLog for each deduction event with the waba_id of the sending channel. Failed messages (non-billable status): no deduction made.If deduction fails due to optimistic lock conflict: retry up to 3 times. If all retries fail: log billing_deduction_failed with organization_id, waba_id, conversation_id; do not block message delivery but flag for manual reconciliation.
3Monthly WABI resetorganization_package_component_initials.remaining_quotaScheduled job runs at the start of each billing cycle (1st day of month or contract anniversary date)Reset remaining_quota in organization_package_component_initials to the subscription-defined initial_quota for all active V3 organizations. Previous remaining WABI is discarded (no carry-over — is_carry_over_monthly = false, is_initial_monthly_reset = true on the WhatsApp BillingComponent). Log wabi_reset_completed per organization with organization_id, old_remaining, new_initial_quota.If reset job fails for an org: retry up to 3 times. If still failing: log wabi_reset_failed with organization_id; alert Bifrost on-call via configured Slack channel. Do not skip or partially reset.
4WAB-Additional carry-over on contract renewalorganization_package_component_additionals.remaining_quotaNew contract ID generated on subscription renewalTransfer existing remaining_quota from the expiring contract's additionals row to the new contract's row. is_carry_over_contract = true on the WhatsApp additional BillingComponent ensures this logic is applied. Log wab_additional_carried_over with old_contract_id, new_contract_id, carried_amount.If carry-over fails: log wab_additional_carry_over_failed; alert on-call. Fail loudly — do not silently discard carry-over balance.
5MCC logs with WABA filterWaConversationLog queryAdmin or Finance user applies waba_id filter on the MCC log view, or API calls GET /api/core/v1/billings/mcc_logs?waba_id=<id>Filter conversation log results to rows where waba_id matches the provided value. waba_id is optional — omitting it returns all logs for the org (existing behavior). billing_report_show_waba_id preference must be ON for the CID; if OFF, filter parameter is ignored and waba_id column is not returned.If waba_id does not match any log for the org: return empty result set with 200. Do not return 404.
6Broadcast deduction report with WABA columnWaConversationLog export query for broadcast typeAdmin clicks "Download CSV" on broadcast deduction reportWhen billing_report_show_waba_id preference is ON for the CID, include waba_id as a column in the CSV. Column maps to WaConversationLog.waba_id for each row. Column is absent from CSV when preference is OFF.If export fails (query timeout for large datasets): return HTTP 504 with message "Export is taking too long. Try reducing the date range." Log broadcast_report_export_timeout.
7Low balance notification triggerOrganizationPackage aggregate balanceDeduction event causes aggregated balance to cross a configured threshold; separate check for balance dropping below 0When aggregated balance (WABI + WAB-Additional + available Postpaid) falls below the configured low-balance threshold: emit low_balance_warning event → send email to Admin + show in-app banner. When aggregated balance drops below 0: emit balance_below_zero event → send a distinct "below zero" email and banner. Same delivery mechanism as current per-WABA notifications.If notification delivery fails: retry up to 3 times with exponential backoff. Log low_balance_notification_failed with organization_id, balance_at_trigger, threshold. Do not block message deduction on notification failure.

HTTP methods, endpoint paths, request/response JSON schemas, and error codes to be resolved by Engineering in the RFC.


8. System Flow + User Stories + ACs

8.1. System Flow

Flow: One CID Multiple WABA — Shared Balance Pool Lifecycle Type: State Lifecycle / System Journey

  1. Billing V3 organization is created with billing_version = "3.0.0" (set via Flipper flags m1_create_subscription_chat/m1_create_subscription_crm in moderator-be).
  2. Company registers one or more WABA IDs via Meta Coexistence embedded signup. Each WABA is stored as a WhatsappPackage row with organization_package_id FK pointing to the same OrganizationPackage record.
  3. Monthly billing cycle starts: WABI reset job runs → organization_package_component_initials.remaining_quota is reset to the subscription-defined max (e.g., 5,000). Previous remaining WABI is discarded.
  4. Agent sends a WhatsApp message from any WABA ID:
    • System identifies the OrganizationPackage via waba_idorganization_package_id lookup.
    • Billing checks if message is billable (non-failed status).
    • If billable: deduct from WABI first (organization_package_component_initials).
    • If WABI exhausted: deduct from WAB-Additional (organization_package_component_additionals).
    • If WAB-Additional exhausted: deduct from Postpaid Limit (organization_package_component_postpaid_limits).
    • If Postpaid Limit also exhausted (0): block further messages; return "Quota Exceeded" error.
    • WaConversationLog record created with waba_id, organization_package_id, credited_to (which bucket), total_price.
  5. Admin views Package Usage page:
    • GET /billings/info returns aggregated balance (sum of all WABAs' buckets for this org).
    • Shared Balance Tooltip is visible; hovering it explains the pool model.
    • Report tables (Broadcast, MCC Logs) show waba_id column when billing_report_show_waba_id preference is ON.
  6. Balance drops below low-balance threshold:
    • System detects aggregated balance crossing threshold post-deduction.
    • low_balance_warning event emitted → email sent to Admin + in-app banner displayed.
    • If balance < 0: balance_below_zero event emitted → distinct below-zero notification sent.
  7. Admin tops up WAB-Additional balance (via Finance PI or self top-up flow):
    • organization_package_component_additionals.remaining_quota increases by the top-up amount.
    • Low balance banner dismissed on next page load.
  8. Contract renewal:
    • New contract ID generated.
    • WAB-Additional remaining_quota is carried over to the new contract period.
    • WABI resets to new subscription max per the monthly reset job.

Failure branches:

  • Postpaid Limit fully consumed (0) → message blocked; quota_exceeded error returned to hub-service.
  • Deduction optimistic lock conflict → retry up to 3 times; if all fail → log billing_deduction_failed, flag for manual reconciliation, message delivery not blocked.
  • Monthly reset job failure → retry 3 times; if still failing → on-call alert; do not silently skip reset.
flowchart TD
Create[V3 Org created\nbilling_version = 3.0.0] --> RegisterWABA[Register WABA IDs via\nMeta Embedded Signup\nWhatsappPackage rows\nlinked via organization_package_id]
RegisterWABA --> CycleStart[Billing Cycle Start:\nWABI Reset Job runs\ninitial_quota restored\nno carry-over]
CycleStart --> Send[Agent sends message\nfrom any WABA ID]
Send --> Billable{Billable\nmessage?}
Billable -- No --> NoDeduct[No deduction]
Billable -- Yes --> CheckWABI{WABI\nremaining > 0?}
CheckWABI -- Yes --> DeductWABI[Deduct from WABI\ncredited_to=wa_balance_initial]
CheckWABI -- No --> CheckAdditional{WAB-Additional\nremaining > 0?}
CheckAdditional -- Yes --> DeductAdditional[Deduct from WAB-Additional\ncredited_to=wa_balance]
CheckAdditional -- No --> CheckPostpaid{Postpaid Limit\n> 0?}
CheckPostpaid -- Yes --> DeductPostpaid[Deduct from Postpaid Limit\ncredited_to=wa_credit]
CheckPostpaid -- No --> Block[Block message\nQuota Exceeded error]
DeductWABI & DeductAdditional & DeductPostpaid --> LogConv[WaConversationLog created\nwaba_id, credited_to, total_price]
LogConv --> ThresholdCheck{Aggregated balance\nbelow threshold?}
ThresholdCheck -- Yes --> LowBal[Emit low_balance_warning\nEmail + in-app banner]
ThresholdCheck -- Below 0 --> BelowZero[Emit balance_below_zero\nDistinct notification]
ThresholdCheck -- No --> Done[Continue]

8.2. User Stories

User StoryImportanceMockup / Technical NotesAcceptance Criteria
[WABA-S01] — WABI Monthly Reset & Deduction Priority

As a System, I want to reset the WhatsApp Initial Balance (WABI) to the subscription-defined max at the start of each billing cycle and always deduct from WABI before any other bucket, so that clients receive their allocated free credits each month and use them first.
Must HaveFigma: N/A — backend logic only

Data Fields:
organization_package_component_initials.remaining_quota (decimal) — resets to initial_quota value on billing cycle start
BillingComponent.is_initial_monthly_reset (bool) — must be true for WhatsApp Initial component
BillingComponent.is_carry_over_monthly (bool) — must be false (no WABI carry-over)
credited_to in WaConversationLog — set to "wa_balance_initial" when deducting from WABI

Before-After Behavior: Before: WABI reset logic exists but was scoped to single-WABA organizations. After: Reset applies to the shared organization_package_component_initials pool for the entire V3 org; all WABA IDs drawing from the same pool benefit from the reset.
— Happy Path —
• AC-1: Given it is the 1st day of the billing cycle for a V3 organization, when the WABI reset job runs, then organization_package_component_initials.remaining_quota is set to the subscription-defined initial_quota (e.g., 5,000) for that org.
• AC-2: Given the reset completes, when the result is logged, then wabi_reset_completed event is emitted with organization_id, old_remaining, and new_initial_quota.
• AC-3: Given a V3 org has 300 WABI remaining at cycle end, when the reset runs, then the 300 remaining credits are discarded and remaining_quota is set to initial_quota — no carry-over.
• AC-4: Given a billable message is sent and WABI remaining_quota > 0, when the deduction runs, then the amount is deducted from organization_package_component_initials (WABI) first; WAB-Additional and Postpaid are not touched.

— Error / Unhappy Path —
• ERR-1: Given the WABI reset job fails for an organization after 3 retries, when the failure persists, then wabi_reset_failed is logged with organization_id and an on-call alert is triggered — the reset is not silently skipped.
• ERR-2: Given a V1 or V2 organization exists, when the reset job runs, then the job does NOT process that organization — only billing_version = "3.0.0" orgs are in scope.

— Permission Model —
• CAN: System (scheduled job); no user action triggers this
• CANNOT: No user can manually trigger or suppress the WABI reset
• Unauthorized: N/A — internal cron job

— UI States —
• N/A — backend job; balance update reflected on next page load of Package Usage
[WABA-S02] — WAB-Additional Purchase & Carry-Over

As a Company Admin, I want to top up "WAB-Additional" balance via Finance PI or self top-up, and have unused WAB-Additional carry over when my contract renews, so that I never lose credits I've paid for.
Must HaveFigma: Figma — Subscription (frame-level TBD)

Data Fields:
organization_package_component_additionals.remaining_quota (decimal) — increases by top-up amount
BillingComponent.is_carry_over_contract (bool) — must be true for WhatsApp Additional component
credited_to in WaConversationLog — set to "wa_balance" when deducting from WAB-Additional

Before-After Behavior: Before: WAB-Additional top-up and carry-over existed for single-WABA orgs. After: Top-up increases the shared organization_package_component_additionals pool for the V3 org; carry-over on renewal transfers the remaining pool — not per-WABA balances — to the new contract period.
— Happy Path —
• AC-1: Given Finance creates a Paid Invoice with +10,000 credits for a V3 org, when the payment is processed, then organization_package_component_additionals.remaining_quota increases by 10,000.
• AC-2: Given a V3 admin completes a self top-up for +10,000 WAB-Additional credits, when the Mekari Pay webhook is received, then organization_package_component_additionals.remaining_quota increases by 10,000.
• AC-3: Given a contract renewal occurs with 6,500 WAB-Additional remaining, when the new contract ID is generated, then the 6,500 balance transfers to the new contract's organization_package_component_additionals.remaining_quota and wab_additional_carried_over is logged with old_contract_id, new_contract_id, carried_amount = 6,500.
• AC-4: Given WABI remaining_quota = 0 and a billable message is sent, when the deduction runs, then credits are taken from organization_package_component_additionals (WAB-Additional) — not from Postpaid.

— Error / Unhappy Path —
• ERR-1: Given the carry-over job fails after 3 retries, when the failure is confirmed, then wab_additional_carry_over_failed is logged and on-call is alerted — the remaining balance is never silently discarded.

— Permission Model —
• CAN: Qontak Finance Team (Paid PI), Company Admin (self top-up via existing self-topup flow)
• CANNOT: CS Agents cannot initiate top-ups
• Unauthorized: Top-up attempt by non-authorized role is rejected at the application level

— UI States —
• N/A — balance update reflected on next Package Usage page load after fulfillment

— Negative Scenarios —
• NEG-1: Given a V1 or V2 org Admin, when a top-up is processed, then the top-up does NOT flow through the V3 component quota tables — it uses the existing wa_balance field on OrganizationPackage unchanged.
[WABA-S03] — Postpaid Limit Configuration & Blocking

As a Qontak Finance Team member, I want to configure a Postpaid Limit for a V3 company, and have the system block messages when that limit is fully consumed, so that clients never exceed their agreed credit ceiling.
Must HaveFigma: N/A — Modpanel configuration; no client-facing UI change

Data Fields:
organization_package_component_postpaid_limits.remaining_quota (decimal) — decreases with each postpaid deduction
organization_package_component_postpaid_limits.initial_quota (decimal) — the configured postpaid ceiling set by Finance
credited_to in WaConversationLog — set to "wa_credit" when deducting from Postpaid Limit

Before-After Behavior: Before: Postpaid limit was configured at WABA level (WhatsappPackage.postpaid_limit). After: Postpaid limit is configured once at the OrganizationPackage component level, applying to the aggregated pool shared by all WABA IDs of the V3 org.
— Happy Path —
• AC-1: Given Finance configures a Postpaid Limit of 3,000 for a V3 org, when WABI and WAB-Additional are both 0 and a billable message is sent, then organization_package_component_postpaid_limits.remaining_quota decreases by the message cost.
• AC-2: Given 2,500 units remain in the Postpaid Limit after deductions, when 500 billable units are consumed, then the Postpaid Limit decreases to 2,000.
• AC-3: Given Postpaid Limit remaining_quota = 0 (fully consumed) and WABI and WAB-Additional are both 0, when a message is attempted, then the API returns a "Quota Exceeded" error — the message is blocked.

— Error / Unhappy Path —
• ERR-1: Given a multi-bucket deduction spans WABI (500), WAB-Additional (400), and Postpaid (100) for a single billing event of 1,000 units, when the deduction runs, then each bucket is reduced by the corresponding amount in a single atomic-like operation — partial deduction from wrong buckets must not occur.

— Permission Model —
• CAN: Qontak Finance Team (configure via Modpanel)
• CANNOT: Company Admin cannot modify Postpaid Limit directly
• Unauthorized: Modpanel access required; unauthorized access to limit configuration returns 403

— UI States —
• N/A — Modpanel configuration; client-facing display covered by CHG-003 (aggregated balance display)
[WABA-S04] — Multi-Bucket Balance Deduction Hierarchy

As a System, I want to deduct usage from the correct bucket in strict order (WABI → WAB-Additional → Postpaid Limit), and never deduct for failed messages, so that clients are billed accurately and in the correct priority.
Must HaveFigma: N/A — backend deduction logic

Data Fields:
WaConversationLog.credited_to (string) — one of "wa_balance_initial", "wa_balance", "wa_credit"; identifies which bucket was used
WaConversationLog.waba_id (string) — WABA ID that originated the conversation
WaConversationLog.is_auto_deduct (bool) — true for system-triggered deductions
• Conversation status field — deduction only occurs for billable statuses (not "failed")

Before-After Behavior: Before: Deduction hierarchy was applied at per-WABA WhatsappPackage level using wa_credit/wa_balance/wa_balance_initial fields. After: Deduction hierarchy operates on shared component quota tables (initialsadditionalspostpaid_limits) for V3 orgs, applying the same priority logic across all WABAs sharing the pool.
— Happy Path —
• AC-1: Given a V3 org has WABI = 500, WAB-Additional = 400, Postpaid = 100, and a billable event of 1,000 units occurs, when deduction runs, then 500 is taken from WABI, 400 from WAB-Additional, and 100 from Postpaid — all three buckets are updated correctly in that order.
• AC-2: Given a conversation has status = "failed", when the billing job evaluates it, then no balance is deducted from any bucket and the WaConversationLog record is created with total_price = 0.
• AC-3: Given the deduction crosses a bucket boundary (WABI partially consumed, remainder from WAB-Additional), when the event is logged, then two WaConversationLog entries are created — one per bucket — or a single entry with credited_to reflecting the primary bucket (Engineering to determine granularity in RFC).
• AC-4: Given a V3 org sends messages from WABA-1 and WABA-2 concurrently, when both deductions run simultaneously, then the optimistic lock mechanism in quota_management/deduction.go ensures the shared pool is not over-deducted — total deducted ≤ available quota.

— Error / Unhappy Path —
• ERR-1: Given an optimistic lock conflict occurs during deduction, when the retry mechanism activates, then the deduction is retried up to 3 times before logging billing_deduction_failed and flagging for manual reconciliation — message delivery is not blocked.
• ERR-2: Given Postpaid Limit is fully consumed, when a message is attempted, then the system returns quota_exceeded error; the WaConversationLog record is NOT created for the blocked message.

— Permission Model —
• CAN: System (automated deduction triggered per message event)
• CANNOT: No user can manually trigger or override the deduction hierarchy
• Unauthorized: N/A — internal system behavior

— UI States —
• N/A — backend only; balance changes visible on next Package Usage page load
[WABA-S05] — WABA ID Column in Usage Reports

As a Company Admin, I want to see which specific WABA ID consumed balance in the Broadcast and Conversation reports, so that I can track per-phone-number usage even though the balance pool is shared.
Must HaveFigma: Figma — Subscription (frame-level TBD — see sample Google Sheet)

Data Fields:
WaConversationLog.waba_id (string) — sourced from the sending channel
billing_report_show_waba_id (bool) — per-CID preference; default OFF
hub-service GET /mcc_logs — accepts optional waba_id query param when preference is ON
hub-service GET /download_broadcast_deduction — includes waba_id in CSV when preference is ON

Before-After Behavior: Before: No waba_id column in Broadcast or Conversation report tables; no per-WABA filter available. After: waba_id column visible in both tables and CSV exports when billing_report_show_waba_id = ON; MCC log table gains a waba_id filter dropdown.
— Happy Path —
• AC-1: Given billing_report_show_waba_id = ON for a V3 CID, when the Package Usage page loads the Broadcast deduction table, then a waba_id column is visible and populated for every row.
• AC-2: Given billing_report_show_waba_id = ON, when the Admin downloads the Broadcast deduction CSV, then every row includes a waba_id field populated with the sending WABA ID.
• AC-3: Given billing_report_show_waba_id = ON, when the Admin views the Conversation/MCC log table, then a waba_id column is visible and a waba_id filter dropdown is available.
• AC-4: Given the Admin filters the MCC log by a specific waba_id, when the filter is applied, then only rows matching that WABA ID are returned.
• AC-5: Given billing_report_show_waba_id = OFF for a CID, when the Admin views Broadcast or Conversation reports, then no waba_id column or filter is shown — the table renders as today.

— Error / Unhappy Path —
• ERR-1: Given the Admin applies a waba_id filter with a value that has no matching logs for the org, when the query returns, then an empty table with "No records found" message is shown — not an error state.
• ERR-2: Given the CSV export times out (> 10s for > 10,000 rows), when the timeout occurs, then the download fails with "Export is taking too long. Try reducing the date range." and broadcast_report_export_timeout is logged.

— Permission Model —
• CAN: Any role with Package Usage page access (Admin, Finance Team, Supervisor where permitted)
• CANNOT: Access is not granted to roles that cannot view Package Usage today — no change to existing role restrictions
• Unauthorized: billing_report_show_waba_id = OFF → column and filter silently absent

— UI States —
• Loading: Skeleton rows while report data fetches
• Empty (no data): "No records found for this period."
• Empty (filter applied, no match): "No records found for WABA ID [id]."
• Error: "Could not load report. Please refresh." + Retry button
• Success: Table with waba_id column populated

— Negative Scenarios —
• NEG-1: Given a V1 or V2 org Admin views the Package Usage report, when the report loads, then no waba_id column or filter is shown regardless of any preference state — V1/V2 UI is unchanged.
[WABA-S06] — Company-Level Low Balance Notifications

As a Company Admin, I want to be notified by email and in-app banner when the aggregated company WhatsApp balance is low, empty, or below zero, so that I can top up before service is interrupted.
Must HaveFigma: Figma — Subscription (Low Balance) (frame-level TBD)

Data Fields:
• Aggregated balance = WABI remaining_quota + WAB-Additional remaining_quota + available Postpaid Limit
low_balance_threshold — configurable per org (TBD — see Open Questions); triggers low_balance_warning event
balance_below_zero event — separate event when aggregated balance < 0
• Notification recipients: Admin role on the company account (same logic as current per-WABA notification recipients)

Before-After Behavior: Before: Low balance notifications were sent per WABA based on individual WABA-level balance. After: Notifications evaluate the aggregated company-level balance — one notification per threshold crossing per company, not per WABA. Delivery mechanism (email + in-app banner) is unchanged.
— Happy Path —
• AC-1: Given the aggregated balance drops below the configured low-balance threshold after a deduction event, when the system evaluates the threshold, then a low_balance_warning event is emitted and the Admin receives an email notification and sees a low-balance banner in the dashboard.
• AC-2: Given the aggregated balance drops below 0 (due to postpaid settlement), when the balance update occurs, then a balance_below_zero event is emitted and a distinct "balance below zero" email and banner are triggered — separate from the standard low-balance notification.
• AC-3: Given the Admin receives a low-balance notification and tops up, when the top-up is fulfilled and the balance rises above the threshold, then the low-balance banner is cleared on the next page load.

— Error / Unhappy Path —
• ERR-1: Given the notification delivery fails after 3 retry attempts, when the failure is logged, then low_balance_notification_failed is recorded with organization_id, balance_at_trigger, and threshold — message deduction is never blocked by notification failure.
• ERR-2: Given the same threshold is crossed multiple times within a single billing cycle due to repeated top-ups and depletions, when the event fires, then the notification must not be duplicate-sent — a cooldown or deduplication mechanism is applied (Engineering to specify in RFC).

— Permission Model —
• CAN: System (emits notification); Admin (receives email and sees banner)
• CANNOT: CS Agents do not receive low balance email notifications (same as today)
• Unauthorized: N/A — system-driven notification

— UI States —
• Banner hidden: Balance above threshold or V1/V2 org
• Low balance banner: Yellow/warning banner displayed in dashboard header area
• Below zero banner: Red/critical banner with "Your WhatsApp balance is below 0" message
• Banner dismissed: Auto-dismissed on next load after balance is restored above threshold
[WABA-S07] — Shared Balance Tooltip on Package Usage Page

As a Company Admin, I want to see an informational tooltip on the Package Usage page explaining that the displayed balance is shared across all my WABA IDs, so that I understand the pool model and don't mistake the total for a single-WABA balance.
Must HaveFigma: Figma — Subscription (frame-level TBD)

Data Fields:
billing_version (string) — gating condition; tooltip rendered only when billing_version = "3.0.0"
billingM1Version (bool) — frontend computed flag in hub-chat's PackageInfo type: billing_version === "3.0.0"
• Tooltip content is static — no API call needed to render it

Before-After Behavior: Before: WhatsApp balance section on Package Usage page shows no contextual explanation of the pool model. After: A static info icon (ⓘ) is displayed next to the WhatsApp balance section heading; on hover/click it shows a tooltip explaining the shared pool model. Visible only for V3 orgs.
— Happy Path —
• AC-1: Given a V3 org Admin (billing_version = "3.0.0") is on the Package Usage page, when the WhatsApp balance section renders, then an info icon (ⓘ) is displayed next to the balance heading.
• AC-2: Given the info icon is visible, when the Admin hovers or clicks it, then a tooltip appears with text explaining: "This balance is shared across all your WhatsApp numbers (WABA IDs). Any message sent by any connected WABA draws from this single pool."
• AC-3: Given a V1 or V2 org Admin is on the Package Usage page, when the WhatsApp balance section renders, then no info icon or tooltip is displayed — the section appears exactly as it does today.

— Error / Unhappy Path —
• ERR-1: Given the billingM1Version flag cannot be resolved (billing info API fails), when the page renders, then the tooltip icon defaults to hidden — fail-safe to avoid rendering confusing UI for non-V3 users.

— Permission Model —
• CAN: Any role with Package Usage page access on a V3 org
• CANNOT: V1/V2 org users — tooltip not rendered
• Unauthorized: N/A — display-only component

— UI States —
• Hidden: billing_version ≠ "3.0.0" or billing info not loaded
• Idle: Info icon displayed next to balance heading
• Active: Tooltip popover visible with explanatory text
• Error: Tooltip icon hidden (fail-safe when billing version unknown)

— Negative Scenarios —
• NEG-1: Given a V1 or V2 org Admin views the Package Usage page, when the page renders, then no tooltip icon is shown — per Non-Goal 2 (V1/V2 not in scope).

AC ids are numbered per story (each story restarts at AC-1). Composite ids minted here: WABA-S01/AC-1 through WABA-S01/AC-4, WABA-S01/ERR-1, WABA-S01/ERR-2; WABA-S02/AC-1 through WABA-S02/AC-4, WABA-S02/ERR-1; WABA-S03/AC-1 through WABA-S03/AC-3, WABA-S03/ERR-1; WABA-S04/AC-1 through WABA-S04/AC-4, WABA-S04/ERR-1, WABA-S04/ERR-2; WABA-S05/AC-1 through WABA-S05/AC-5, WABA-S05/ERR-1, WABA-S05/ERR-2; WABA-S06/AC-1 through WABA-S06/AC-3, WABA-S06/ERR-1, WABA-S06/ERR-2; WABA-S07/AC-1 through WABA-S07/AC-3, WABA-S07/ERR-1.


9. Rollout

FieldValue
Feature flagbilling_report_show_waba_id — default: OFF. Enabled per CID by Bifrost ops to surface the waba_id column in reports. The shared balance pool, deduction hierarchy, and tooltip are active for all V3 orgs (billing_version = "3.0.0") without a separate toggle.
Billing version gatebilling_version = "3.0.0" is a hard gate for all shared-pool behaviors. V1/V2 orgs are entirely unaffected — no rollout risk to existing clients.
Stage 1Internal Bifrost test V3 CIDs only (≤5 accounts, manually enabled) — validate deduction hierarchy, WABI reset, and aggregated balance display
Stage 2Closed beta: 5–10 selected Billing V3 clients with multiple WABA IDs registered — validate tooltip UX, report columns, and low balance notifications with real data
Stage 3All Billing V3 clients in batches (25% → 50% → 100%) — enable billing_report_show_waba_id per batch
GAAll Billing V3 clients — billing_report_show_waba_id = ON by default for new V3 CIDs; existing V3 CIDs migrated in batch
Backward compatYes — V1/V2 orgs are completely unaffected. All per-WABA wa_credit/wa_balance/wa_balance_initial fields on WhatsappPackage remain unchanged and functional for non-V3 orgs.
MigrationWhatsappPackage.organization_package_id FK already exists (migration 20260113000000). Remaining migration: seed billing_report_show_waba_id preference row for all V3 CIDs (default OFF). No balance data migration required — the component quota tables are already populated for active V3 orgs.

9.1. Migration Transition Window

FieldDetail
Old record behaviorWaConversationLog records created before this PRD ships do not have meaningful waba_id population for multi-WABA aggregation. These records appear in reports with whatever waba_id was stored at creation time — no backfill is performed.
New record behaviorAll WaConversationLog records created after GA have waba_id correctly populated and linked to the shared organization_package_id pool.
Coexistence periodFrom Stage 1 until all V3 CIDs are migrated to GA (estimated 4–6 weeks). During this window, some V3 orgs have the new pool model while others still see old per-WABA display — gated by billing_report_show_waba_id preference.
End stateAll V3 CIDs on shared pool model with billing_report_show_waba_id = ON. Historical logs pre-GA remain as-is with no retrospective waba_id enrichment.

10. Observability

Key Events:

Event NameTriggerProperties
wabi_reset_completedMonthly WABI reset job completes for an orgorganization_id, old_remaining, new_initial_quota, timestamp
wabi_reset_failedWABI reset job fails after 3 retriesorganization_id, error, timestamp
wab_additional_carried_overWAB-Additional carry-over on contract renewalorganization_id, old_contract_id, new_contract_id, carried_amount
wab_additional_carry_over_failedCarry-over fails after 3 retriesorganization_id, error, timestamp
billing_deduction_completedSuccessful multi-bucket deductionorganization_id, waba_id, conversation_id, credited_to, amount, buckets_used (array)
billing_deduction_failedDeduction fails after all retriesorganization_id, waba_id, conversation_id, error
quota_exceededMessage blocked due to all buckets exhaustedorganization_id, waba_id
low_balance_warningAggregated balance crosses low-balance thresholdorganization_id, aggregated_balance, threshold
balance_below_zeroAggregated balance drops below 0organization_id, aggregated_balance
low_balance_notification_failedNotification delivery fails after retriesorganization_id, balance_at_trigger, threshold, error
broadcast_report_export_timeoutCSV export times outorganization_id, row_count_estimate, date_range
billing_aggregate_balance_timeoutAggregate balance query exceeds 500ms p95organization_id, query_duration_ms
FieldDetail
Dashboard ownerBifrost team — existing Datadog billing dashboard; add panels for wabi_reset_*, billing_deduction_failed, quota_exceeded, low_balance_* event rates
Alert 1billing_deduction_failed rate > 0.1% of deduction events in 5-minute window → PagerDuty: Bifrost on-call
Alert 2wabi_reset_failed for any org → Slack: #bifrost-billing-alerts within 15 minutes of job run
Alert 3billing_aggregate_balance_timeout rate > 1% of requests in 5-minute window → PagerDuty: Bifrost on-call

10.1. Post-Launch Monitoring Cadence

FieldDetail
Review cadenceWeekly for first 4 weeks post-GA, then monthly
OwnerBifrost PM + Bifrost Eng Lead
Review scopewabi_reset_completed/failed counts; billing_deduction_failed rate; quota_exceeded rate; low_balance_warning and balance_below_zero event volume; billing_aggregate_balance_timeout rate
Trigger threshold 1billing_deduction_failed rate > 0.5% in any week → immediate investigation; PM disables billing_report_show_waba_id preference for affected CIDs pending root cause
Trigger threshold 2wabi_reset_failed for > 3 organizations in a single cycle → escalate to engineering sprint priority; affected orgs receive manual reset within 24 hours
Rollback considerationIf billing_deduction_failed or quota_exceeded rates exceed thresholds and cannot be resolved within 4 hours, PM disables the shared-pool deduction path for affected CIDs by reverting billing_version check in the deduction service — decision made jointly with Eng Lead.

11. Success Metrics

Stability & Accuracy:

MetricDefinitionBaselineTarget
Billing deduction accuracy% of billable conversations with correct bucket deduction (verified by WaConversationLog.credited_to audit vs. expected order)N/A — new multi-bucket model for V3100% accurate within 30 days of GA; zero deduction order violations reported
Zero critical incidentsCount of P0/P1 incidents related to balance deduction, WABI reset, or notification in first 30 daysN/A0 P0/P1 incidents in first 30 days post-GA
WABI reset reliability% of V3 orgs for which WABI reset completes successfully on billing cycle startN/A≥ 99.9% successful resets per cycle

Adoption & Engagement:

MetricDefinitionBaselineTarget
Report waba_id column visibility% of V3 multi-WABA CIDs with billing_report_show_waba_id = ON0% (new feature)≥ 70% of eligible V3 CIDs enabled within 60 days of GA
Tooltip interaction rate% of V3 Admin sessions on Package Usage page that interact with the Shared Balance Tooltip (hover/click)0% (new feature)≥ 20% of V3 sessions within 30 days of GA (signals comprehension of pool model)

Business Impact:

MetricDefinitionBaselineTarget
Finance reconciliation timeAvg time for Finance team to confirm total WhatsApp credit availability for a multi-WABA clientTBD (currently manual; ≥ 5 min estimated per client)≤ 1 minute (single aggregated view replaces multi-WABA lookup)

12. Launch Plan & Stage Gates

StageAudienceDurationSuccess Gate to AdvanceOwner
Internal Alpha≤5 Bifrost internal V3 test CIDs2 weeks0 P0 bugs. WABI reset runs correctly on cycle start. Multi-bucket deduction order verified (WABI → Additional → Postpaid). Aggregated balance display matches manual sum of per-WABA balances. Tooltip renders for V3 and is absent for V1/V2.Bifrost PM + QA
Closed Beta5–10 V3 clients with ≥2 registered WABA IDs2 weeksBilling deduction accuracy = 100% (verified via WaConversationLog audit). Low balance notification received within 5 minutes of threshold crossing. waba_id column visible and correctly populated in both report types. 0 P0/P1 incidents.Bifrost PM
Staged RolloutAll V3 CIDs in batches (25% → 50% → 100%) enabling billing_report_show_waba_id3 weeksbilling_deduction_failed rate < 0.1%. wabi_reset_failed rate = 0. V1/V2 org behavior verified unchanged by sampling ≥20 non-V3 CIDs.Bifrost PM + Eng Lead
GAAll V3 CIDsOngoingAll staged rollout gates sustained for 2 consecutive weeks. Tooltip interaction rate ≥ 20%. Finance team confirms reconciliation time ≤ 1 min for multi-WABA clients. PMM acknowledgement received.Bifrost PM

13. Dependencies

DependencyOwning TeamDeliverable NeededBlocking?
WhatsappPackage.organization_package_id FK populated for all V3 orgs — migration 20260113000000 adds the column but all existing V3 WhatsappPackage rows must have this FK correctly set before the shared-pool deduction logic runsBifrost EngData backfill script to set organization_package_id on all existing V3 WhatsappPackage rows; verification query confirming 100% FK coverage before Stage 1YES — deduction and aggregation blocked without FK
billing_report_show_waba_id preference available in OrganizationPreference — new preference key must be seedable per CID before Staged Rollout beginsBifrost EngPreference key defined in hub-service preference registry; Modpanel UI or script to enable per CIDYES for WABA-S05 — report column gating blocked without preference
Low balance threshold configuration — the specific numeric threshold per org (or a global default) for triggering low_balance_warning must be decided and configurable before notification events can be emitted in productionBifrost PM + Finance TeamDecision on default threshold value and whether it's configurable per org or global (see Open Questions Q1)YES for WABA-S06 — notification logic blocked without threshold definition
Figma frame-level designs — existing Figma file linked in Header is present; frame-level links for each UI change (CHG-001–003, tooltip, report columns) required before RFCWulan Febyazzahra Putri (Bulan)Frame-level Figma URLs for all new/modified UI componentsYES for RFC — Engineering needs component specs before RFC
Meta Coexistence embedded signupWhatsappEmbeddedSignup in hub-service must correctly link each new WABA to the org's organization_package_id; the multi-WABA onboarding flow (POST /core/v1/whatsapp_embedded_signup/waba) must set this FK on creationBifrost / hub-service EngConfirm that POST /waba in embedded signup correctly sets organization_package_id; fix if notYES — without this, newly registered WABAs won't share the pool

14. Key Decisions + Alternatives Rejected

14a — Decisions Made

DateDecisionRationale
2026-03-11Shared Pool Model — all WABA IDs under a company draw from a single aggregated balance, not individual per-WABA poolsMeta Coexistence requires companies to treat all WABAs as part of one account; per-WABA isolation would contradict the coexistence model and force clients to manually rebalance credits across WABAs
2026-03-11Billing V3 only — shared pool behavior is gated on billing_version = "3.0.0"; V1/V2 orgs are entirely unaffectedV1/V2 organizations use a different billing architecture (wa_credit/wa_balance flat fields); migrating them would require a separate initiative with higher risk
2026-03-11Deduction priority: WABI → WAB-Additional → PostpaidWABI is the subscription-included free allocation and must be consumed first; WAB-Additional is prepaid and should be used before incurring postpaid credit; Postpaid is the overdraft safety net of last resort
2026-06-29waba_id column gated by billing_report_show_waba_id preferenceNot all V3 orgs have multiple WABAs yet; showing the column by default when it has only one value adds noise. Preference allows controlled rollout and opt-in for orgs that need per-WABA visibility

14b — Alternatives Rejected

AlternativeWhy RejectedDate
Per-WABA balance isolation with manual rebalancing — give each WABA its own sub-quota that Finance can redistributeAdds operational complexity (Finance must manage N quotas per company instead of 1), contradicts the Coexistence pooling intent, and creates service interruption risk when one WABA runs out while others have surplus2026-03-11
Display both aggregated and per-WABA breakdown simultaneously — show total + per-WABA split table on Package Usage pageOverwhelming for Admins; per-WABA breakdown is available via the waba_id column in reports, which is the appropriate granular view; the balance page should show actionable credit total, not a breakdown that implies per-WABA control2026-03-11
Apply shared pool to V1/V2 orgs via migration — migrate all existing clients to the new architecture in one goToo high risk; V1/V2 billing uses different fields (wa_credit, wa_balance on WhatsappPackage); a forced migration without a standalone initiative for V1→V3 migration would break existing billing flows and require significant rollback planning2026-03-11
No low balance notification change (keep per-WABA notifications) — continue sending one notification per WABA instead of one per companyPer-WABA notifications become meaningless when balance is pooled — receiving "WABA-1 is low" when the shared pool is fine is a false alarm; aggregated notification aligns with the shared-pool mental model2026-03-11

15. Open Questions

#TypeQuestionOwnerDeadline
1Open QuestionWhat is the default low-balance threshold for triggering low_balance_warning? Is it a fixed global value (e.g., 10% of subscription WABI max), a Finance-configurable value per org, or a fixed amount (e.g., 500 credits)? This decision blocks WABA-S06 implementation.Bifrost PM + Finance TeamTBD — before RFC
2Open QuestionFor deductions that span multiple buckets in a single billing event (e.g., 500 from WABI + 300 from WAB-Additional), should the WaConversationLog create one record per bucket or a single record with the primary bucket in credited_to? The answer determines the reporting granularity and log schema.Bifrost PM + EngTBD — before RFC
3Open QuestionShould the billing_report_show_waba_id preference be enabled by default for all new V3 CIDs, or remain OFF until explicitly enabled? If ON by default, does this require a customer communication/changelog entry?Bifrost PM + PMMTBD — before GA
4AssumptionThe WhatsappEmbeddedSignup flow in hub-service (POST /core/v1/whatsapp_embedded_signup/waba) correctly sets organization_package_id on new WhatsappPackage rows. If this FK is not being set at WABA registration time, the shared pool deduction will silently fail to aggregate new WABAs. This assumption must be verified by engineering before Stage 1.Bifrost EngBefore Stage 1
5RiskThe notification deduplication mechanism for low_balance_warning (preventing duplicate sends when threshold is crossed multiple times within a billing cycle) is not specified. Without this, Finance teams could receive spam notifications during volatile balance periods. Mitigation: Engineering to define a per-org per-cycle deduplication window in the RFC.Bifrost EngBefore RFC — must have mitigation before READY
6AssumptionWhatsappUsageComparison table (CID vs Meta reconciliation) already tracks cid + waba_id per the existing migration 20260223000000. This table is used for Meta reconciliation only and does not need changes to support the shared pool model. To be confirmed by engineering.Bifrost EngBefore RFC

PRD CHANGELOG

VersionDateBySectionTypeSummary
1.02026-06-29ClaudeAllCREATEDInitial NEW PRD generated from Confluence source (BIF-6428) with full technical context from qontak-billing, moderator-be, hub-service, and hub-chat repositories