WhatsApp Campaign | MM Dynamic Pricing — Phase 1: Bid-Based Messaging
HEADER BLOCK
| Field | Value |
|---|---|
| PM | Evelin Suwantio |
| PRD Version | 1.0 |
| Status | DRAFT |
| PRD Type | NEW |
| Epic | QC-20757 |
| Squad | Revenue Squad |
| RFC Link | In progress — not yet published |
| Figma Master | N/A — pending design. Reference: "New Pricing feature - Marketing Messages | Suggested UI changes (1).pdf" attached on the source Confluence page |
| Anchor | No — standalone, single-squad |
| Labels | epic:whatsapp-campaign-dynamic-pricing | module:wa-campaign | feature:bid-based-messaging |
| Last Updated | 2026-07-03 |
Source: Reformatted from the legacy Confluence draft WhatsApp Campaign: MM Dynamic Pricing (Bid-Based Messaging) - Draft. Eng 1-Pager: Confluence. Meta pricing reference: Official Dev Doc.
✅ Reformat Complete — No Flags
All mandatory sections were filled during the coaching interview. This PRD meets the same standard as a write-prd output. No outstanding GAP FLAGS remain — the residual unknowns are captured as tracked Risk rows in Section 16 (Open Questions), each with a mitigation path.
Table of Contents
- HEADER BLOCK
- 2. One-liner + Problem
- 3. Target Users + Persona Context
- 4. Non-Goals
- Scope Changes
- 5. Constraints
- 6. Feature Changes
- 7. New Features
- Stitch UI Prompts
- 8. API & Webhook Behavior
- 9. System Flow + User Stories + ACs
- 10. Rollout
- 11. Observability
- 12. Success Metrics
- 13. Launch Plan & Stage Gates
- 14. Dependencies
- 15. Key Decisions + Alternatives Rejected
- 16. Open Questions
- PRD CHANGELOG
2. One-liner + Problem
One-liner: Enable Campaign Admins at MM API companies to set a maximum bid price for WhatsApp marketing messages, replacing Meta's fixed per-message pricing.
Problem: Meta is replacing WhatsApp marketing messages' fixed per-message pricing with a dynamic bid-based model, where a message is only delivered if the business's bid meets or exceeds Meta's reserved price for that recipient. Campaign Admins at MM API companies today have no way to control this trade-off — they either overpay for low-demand recipients or lose delivery entirely to high-demand recipients, with no visibility into why a message didn't land. Without a bid-strategy mechanism in place before Meta's model goes live, businesses risk degraded delivery rates and unpredictable WhatsApp Balance spend.
3. Target Users + Persona Context
| Persona | Role | Goal | Pain | Workaround |
|---|---|---|---|---|
| Primary — Campaign Admin | Marketing/Ops staff at MM API companies who create and send WA marketing campaigns | Maximize message delivery to customers while controlling per-message spend | Under fixed pricing, either overpays for low-demand recipients or can't reach high-demand recipients at all — no lever to control this trade-off, and no visibility into why specific recipients don't get delivered | Accepts one flat price for the whole list; over-provisions WA Balance as a buffer against running out mid-campaign; manually cross-references delivery reports to guess which recipients failed and why |
(See Constraints for plan availability and feature flag scope.)
4. Non-Goals
- Dynamic bid-based pricing does not apply to WhatsApp Cloud API — MM API only.
- Sending marketing messages via Followup type in Inbox is not covered — always uses Default Price.
- Campaigns scheduled via "Send recurring" before this feature's release default to the Default Price bidding option and cannot retry failed recurring sends.
- Manual, per-recipient bid amounts are not supported — the bid amount applies uniformly to the entire recipient list.
- Retry is capped at 3 total attempts (1 automatic + 2 manual) — unlimited or indefinite retry is out of scope.
- Country-specific bid floors/ceilings follow Meta's published per-country marketing price automatically — manual admin override of these bounds is out of scope.
- Real-time per-message invoice reconciliation is out of scope — reconciliation runs on a monthly cadence against Meta's invoice.
Scope Changes
Engineering surfaces this PRD touches (controlled vocab). Kept in sync with the scope_changes frontmatter above.
- Backend — bid strategy storage on campaigns; local bid-too-low detection heuristic (Meta returns no dedicated error code); retry orchestration (1 automatic + 2 manual, max 3 attempts); WA Balance deduction by reserved price (from delivered/read webhook) with monthly reconciliation against Meta's invoice.
- Frontend — Bid Strategy selector step in campaign creation (Send now/Send later); retry banner + manual bid input on Campaign Detail; Bid Information and Bid Performance Insight panels on Campaign Detail.
- Design — Figma for the bid strategy selector and the Campaign Detail additions (currently TBD — see Header Block).
5. Constraints
| Field | Value |
|---|---|
| Platform | Web only (Qontak dashboard) |
| Performance | Bid strategy price preview must render ≤2s during campaign creation |
| Data limits | Bid amount bounds derived from Meta's published per-country marketing-message price: minimum = published price for that country (e.g. Rp300 for Indonesia); maximum = 2× that published price. Bounds are per recipient's country, not a fixed universal value. |
| Plan scope | MM API companies only (WA Cloud API companies excluded — see Non-Goals) |
| Feature flag | wa_dynamic_pricing_bid_strategy | default: OFF, enabled per company_id |
| Read/write | Campaign Admin: read + write bid strategy/amount. All other roles: read-only on Campaign Detail's Bid Information panel. |
6. Feature Changes
Change ID: CHG-001
| Field | Detail |
|---|---|
| Change Type | Modified page — Campaign Detail; Modified backend behavior — WA Balance deduction |
| Page | /campaigns/{id}/detail |
| Page Intent | Campaign Admin reviews delivery status, cost, and can recover failed recipients |
| Before | • WA Balance is deducted a fixed per-message price regardless of demand. • Campaign Detail shows delivery status per recipient with no bid/price context. |
| After | • WA Balance is deducted by Meta's reserved price (reported via delivered/read webhook), reconciled monthly against Meta's invoice. • Campaign Detail additionally shows a Bid Information panel, Bid Performance Insight, a retry banner for low-bid failures, and a retried-recipient icon per row. |
| Element | Before | After |
|---|---|---|
| Balance deduction | Fixed price per message | Reserved price per message (webhook-reported, reconciled monthly vs. invoice) |
| Campaign Detail | No bid/price info | Bid Information + Bid Performance Insight + retry banner + retried-recipient icon |
Figma: Pending — see Stitch Prompts section (Frame-level link — see Header.)
7. New Features
Feature: Bid Strategy Selector (new step inside WA Campaign creation, "Send now"/"Send later" only)
| Field | Detail |
|---|---|
| URL | /campaigns/create (existing page, new section) |
| Access | Campaign Admin role, for companies with MM API active |
Component Tree:
| Component | Parent | Purpose |
|---|---|---|
| BidStrategyStep | — | New step in the campaign creation flow |
| BidStrategyRadioGroup | BidStrategyStep | Presents Cost Saver / Default / Aggressive / Custom, each with an indicative price |
| CustomBidAmountInput | BidStrategyRadioGroup | Shown only when Custom is selected; enforces per-country min/max |
| BidPriceExplainerTooltip | BidStrategyStep | Explains "you'll be charged the Reserved Price, not your Bid Amount" |
UI States:
| State | Description |
|---|---|
| Empty | No historical price yet for this recipient list → Cost Saver/Aggressive show static defaults (±10% off/on the Default price, e.g. Rp536.69 / Rp655.96 for a Rp596.33 Indonesia example) |
| Loading | Skeleton while the system fetches historical reserved-price data to compute strategy prices |
| Error | Price-fetch fails → only "Default" is shown; Cost Saver/Aggressive disabled with tooltip "Pricing unavailable — try again later" |
| Success | All 4 strategies shown with computed indicative prices |
Figma: Pending — see Stitch Prompts section
Feature: Retry Failed Messages panel (Campaign Detail)
| Field | Detail |
|---|---|
| URL | /campaigns/{id}/detail |
| Access | Campaign Admin who owns the campaign, MM API companies only |
Component Tree:
| Component | Parent | Purpose |
|---|---|---|
| RetryBidBanner | CampaignDetailPage | Shown when failed-due-to-low-bid recipients exist |
| RecommendedBidAmount | RetryBidBanner | Displays the system-recommended (highest known) reserved price |
| ManualBidAmountInput | RetryBidBanner | Editable bid field with live "x of y will be retried" estimate |
| RetryConfirmButton | RetryBidBanner | Submits a manual retry; disabled after 3 total attempts used |
UI States:
| State | Description |
|---|---|
| Empty | No failed-low-bid recipients → banner not shown |
| Loading | Spinner on retry button while resend is in flight |
| Error | Retry API call fails (system error, not bid-too-low) → inline error, no retry attempt consumed |
| Success | Recipient status updates to "Retried" with icon shown in the recipient list |
Figma: Pending — see Stitch Prompts section
Feature: Bid Information & Performance Insight (Campaign Detail)
| Field | Detail |
|---|---|
| URL | /campaigns/{id}/detail |
| Access | Campaign Admin (read/write), other roles (read-only) — Type:Campaign, Category:Marketing, MM API companies only |
Component Tree:
| Component | Parent | Purpose |
|---|---|---|
| BidInformationPanel | CampaignDetailPage | Shows strategy used, price, retry amount, retry-by user + timestamp |
| BidPerformanceInsight | CampaignDetailPage | Shows total IDR saved vs. Default price |
| RetriedRecipientIcon | RecipientListRow | Per-row indicator that a recipient was retried |
UI States:
| State | Description |
|---|---|
| Empty | Campaign not yet sent → "Bid performance available once campaign starts sending" |
| Loading | Skeleton for both panels |
| Error | Savings calculation fails → "—" shown with tooltip "Unable to calculate savings" |
| Success | Full panel with computed values |
Figma: Pending — see Stitch Prompts section
📊 UI State Diagram — Bid Strategy Selector
stateDiagram-v2
[*] --> Loading: Campaign Admin opens Bid Strategy step
Loading --> Empty: No historical price yet for recipient list
Loading --> Success: Historical price available
Loading --> Error: Price-fetch fails
Empty --> Success: Static ±10% defaults shown
Error --> Loading: Campaign Admin retries
Success --> [*]: Campaign Admin selects a strategy
Stitch UI Prompts
[Claude inserts the Stitch prompt table here at READY time. Leave blank until then.]
8. API & Webhook Behavior
| # | Behavior | Entity Affected | Triggered By | Expected Behavior | Failure Behavior |
|---|---|---|---|---|---|
| 1 | Submit campaign with bid strategy | Campaign + recipient list | Campaign Admin selects a strategy and clicks Send now/Send later | Bid strategy + bid amount stored on the campaign; applied to every recipient in the list; messages sent to Meta carrying the bid amount | Custom amount outside [per-country published price, 2× that price] → blocked client-side with a validation message; logs bid_validation_failed |
| 2 | Meta reports delivery + price outcome per message | Message | Meta's async delivered/read webhook | Reserved price recorded against the message (near-real-time); used for Bid Performance Insight and WA Balance deduction | Confirmed: failed messages return no billable price from Meta — consistent with the no-charge-on-failure model. The webhook-reported price is indicative; Meta's monthly invoice is the source of truth for final billing (data-processing variance, same as Meta's Analytics API) — see Section 16 Risk. |
| 3 | Retry failed messages (bid too low) | Failed recipient subset of a campaign | System detects bid < recipient's last known reserved price (Meta provides no dedicated error code for this failure) | System automatically retries once using the system-recommended (highest known) price. If it fails again, Campaign Admin can manually retry up to 2 more times with a new bid amount (max 3 total attempts), each showing a live "x of y will be retried" estimate | Retry count already at 3 → retry disabled, recipient marked permanently failed for this campaign. Still bid < reserved price after any attempt → fails again, consumes one attempt. |
| 4 | Deduct WA Balance per delivered/read message | Account WA Balance | Meta confirms delivered/read with a reserved price | Balance deducted by the webhook-reported reserved price, not the bid amount; reconciled monthly against Meta's invoice | Discrepancy vs. monthly invoice beyond tolerance (>5%/day) → reconciliation adjustment applied, logged as wa_balance_reconciliation_mismatch. New deduction flow for Pending/Sent status is pending Bifrost's 1-pager (Section 14, blocking) — until live, deduction continues on the current at-delivery model. |
Claude resolves during RFC: HTTP method, path, request/response JSON schema, error codes.
📊 API Sequence — Bid Submission & Reserved-Price Deduction
sequenceDiagram
participant CA as Campaign Admin
participant Q as Qontak System
participant M as Meta MM API
CA->>Q: Select bid strategy + amount, click Send
Q->>Q: Validate bid amount (per-country min/max)
Q->>M: Dispatch messages with bid amount per recipient
M-->>Q: Delivered/read webhook + reserved price
Q->>Q: Deduct WA Balance by reserved price
Note over Q: Reconcile vs. Meta invoice (monthly)
M-->>Q: Failed (bid < reserved price), no price returned
Q->>Q: Infer bid-too-low locally (no Meta error code)
Q->>M: Auto-retry once at recommended price
alt Still fails
Q-->>CA: Show retry banner, allow up to 2 manual retries
end
9. System Flow + User Stories + ACs
9.1. System Flow
Flow: Campaign Admin Sets Bid Strategy and Sends WA Marketing Campaign Type: User Journey + API Sequence
- Campaign Admin creates a WA marketing campaign (Send now/Send later) on an MM API company and selects a recipient list.
- System shows 4 bid strategy options (Cost Saver, Default, Aggressive, Custom), each with an indicative price.
- Campaign Admin selects a strategy or enters a Custom amount (bounded: per-country published price → 2× that price).
- System validates and stores the bid amount, applied uniformly to all recipients.
- Campaign Admin sends; system dispatches messages to Meta's MM API carrying the bid amount.
- Per recipient, Meta evaluates
bid ≥ reserved price→ delivered, charged the reserved price (reported via delivered/read webhook). - Failure branch:
bid < reserved price→ not delivered, no charge; system infers this locally (no Meta error code exists) by comparing bid to the recipient's last known reserved price. - System automatically retries the failed recipient once, using the system-recommended (highest known) price.
- Failure branch: If still failing, Campaign Detail shows a retry banner; Campaign Admin can manually retry up to 2 more times with an adjusted bid (max 3 attempts total).
- On each successful delivery, WA Balance is deducted by the webhook-reported reserved price; reconciled monthly against Meta's invoice.
- Campaign Detail surfaces Bid Information, Bid Performance Insight (IDR saved), and a retried-recipient icon.
- Failure branch: All 3 attempts exhausted and still failing → recipient marked permanently failed for this campaign, no further retry.
📊 System Flow — WA Campaign Bid Strategy & Delivery
sequenceDiagram
participant CA as Campaign Admin
participant Q as Qontak System
participant M as Meta MM API
CA->>Q: Create campaign, select recipient list
Q->>CA: Show 4 bid strategies with indicative prices
CA->>Q: Select strategy / enter Custom amount
Q->>Q: Validate bid (per-country min/max)
CA->>Q: Click Send
Q->>M: Dispatch messages with bid amount
alt bid >= reserved price
M-->>Q: Delivered/read webhook + reserved price
Q->>Q: Deduct WA Balance by reserved price
Q->>CA: Show Bid Information + Performance Insight
else bid < reserved price
M-->>Q: Not delivered, no charge
Q->>Q: Infer bid-too-low locally (no Meta error code)
Q->>M: Auto-retry once at recommended price
alt Auto-retry still fails
Q->>CA: Show retry banner (up to 2 manual retries)
loop up to 2 manual retries
CA->>Q: Submit new bid amount
Q->>M: Resend with new bid
end
alt All 3 attempts exhausted
Q->>CA: Mark recipient permanently failed
end
end
end
9.2. User Stories
| User Story | Importance | Mockup | Technical Notes | Acceptance Criteria |
|---|---|---|---|---|
| [WABID-S01] — Select Bid Strategy for Campaign As a Campaign Admin, I want to select a bid strategy (Cost Saver/Default/Aggressive/Custom) when creating a WA marketing campaign, so that I control how much I'm willing to pay to reach each customer. | Must Have | Pending — see Stitch Prompts | Data Fields: • strategy (enum, required) — User input• bid_amount (decimal, required if custom) — User input• company_id (Auth session)• country (Account settings — determines price floor/ceiling)Before-After Behavior: Campaigns previously always used Meta's fixed per-message price with no user control; after this story, Campaign Admins choose from 4 bid strategies with a mandatory bid amount. | — Happy Path — • AC-1: Given a Campaign Admin creating a campaign on an MM API company, when they open campaign creation, then they see 4 strategies each with an indicative price. • AC-2: Given they select Custom, when they enter an amount within [per-country published price, 2× that price], then it's accepted and stored. • AC-3: Given they don't change the strategy, when they proceed to send, then "Default" is applied automatically with its indicative price. • AC-4: Given a strategy is already selected, when they change it before clicking Send, then the new choice replaces the old one with no restriction. — Error / Unhappy Path — • ERR-1: Given Custom is selected, when the amount is outside the valid range, then submission is blocked with a validation message showing the valid range, and logs bid_validation_failed.— Permission Model — • CAN: Campaign Admin on MM API companies • CANNOT: Campaign Admin on Cloud-API-only companies; read-only roles • Unauthorized: Bid Strategy step not rendered; falls back to Meta's default fixed pricing — UI States — • Loading: Skeleton while historical price data loads • Empty: No historical price yet → Cost Saver/Aggressive show static ±10% defaults • Error: Price-fetch fails → only Default shown, others disabled with tooltip • Success: All 4 strategies shown with computed prices — Negative Scenarios — (from Non-Goals) • NEG-1: Given a company on Cloud API (not MM API), when they create a campaign, then no bid strategy step is shown and Non-Default pricing does not apply. • NEG-2: Given a Campaign Admin wants a per-recipient bid, when they use the recipient list, then only a single uniform bid amount applies to the whole list. |
| [WABID-S02] — Retry Failed Messages with Adjusted Bid As a Campaign Admin, I want to retry messages that failed because my bid was too low, so that I can still reach those customers. | Must Have | Pending — see Stitch Prompts | Data Fields: • recipient_id, retry_count (0-3)• new_bid_amount (decimal, manual retries) — User input• failure_reason (derived locally — see Section 16 Risk on no Meta error code)Before-After Behavior: Previously, failed recipients had no recovery path; after this story, 1 automatic retry fires, then up to 2 manual retries with a new bid (3 total). Note: once submitted, a retry cannot be cancelled — the only "undo" is that failed sends incur no charge (WABID-S04 AC-2). | — Happy Path — • AC-1: Given a message fails (bid < last known reserved price), when detected, then system auto-retries once using the recommended (highest known) price, retry_count→1. • AC-2: Given the auto-retry also fails, when Campaign Admin opens Campaign Detail, then they see a retry banner "Message fails to deliver. You need at least Rp xx to reach this customer," editable bid field, and a live "x of y will be retried" estimate. • AC-3: Given a manual retry is submitted and retry_count < 3, then message resends and count increments; at count=3 and still failing, recipient is marked permanently failed. — Error / Unhappy Path — • ERR-1: Given retry_count = 3, when another retry is attempted, then the control is disabled with tooltip "Maximum retries reached," no API call made. • ERR-2: Given a manual retry bid is still below the per-country minimum, then it's blocked (same validation as WABID-S01 ERR-1). — Permission Model — • CAN: Campaign Admin who owns the campaign, MM API companies • CANNOT: Cloud-API companies; read-only roles • Unauthorized: Retry controls not rendered — UI States — • Loading: Spinner on retry button • Empty: No failed-low-bid recipients → no banner • Error: Retry API call fails (system error, not bid-too-low) → inline error, no attempt consumed • Success: Recipient status → "Retried" with icon — Negative Scenarios — (from Non-Goals) • NEG-1: Given a recipient has exhausted all 3 attempts, when the Campaign Admin tries to retry again, then no further retry is offered this campaign. • NEG-2: Given a "Send recurring" campaign scheduled before this feature's release, when a recipient fails, then no retry is offered. |
| [WABID-S03] — View Bid Information and Performance Insight As a Campaign Admin, I want to see bid strategy, retry history, and cost savings on Campaign Detail, so that I can evaluate whether my bidding strategy is working. | Should Have | Pending — see Stitch Prompts | Data Fields: • bid_strategy, bid_price, retry_amount (NULL if none), retried_by (user+timestamp)• saved_amount_idr = Σ(delivered/read) × (default_price − reserved_price)Before-After Behavior: Campaign Detail previously showed no bid data; after this story, it surfaces bid strategy, retry history, and computed savings — Type:Campaign, Category:Marketing, MM API only. | — Happy Path — • AC-1: Given a Type:Campaign/Category:Marketing campaign on MM API, when Campaign Admin opens Campaign Detail, then Bid Information (strategy, price, retry amount, retry-by) is shown. • AC-2: Given ≥1 delivered/read message priced below Default, then Bid Performance Insight shows total IDR saved. • AC-3: Given a recipient was retried, then a retried-recipient icon shows on that row. — Error / Unhappy Path — • ERR-1: Given the campaign is Cloud-API-only or not Type:Campaign/Category:Marketing, then the Bid Information panel is not rendered. — Permission Model — • CAN: Campaign Admin, read-only viewers (MM API companies) • CANNOT: Roles on Cloud-API-only companies • Unauthorized: Panel not rendered — UI States — • Loading: Skeleton for both blocks • Empty: Campaign not yet sent → "Bid performance available once campaign starts sending" • Error: Savings calc fails → "—" with tooltip • Success: Full panel with computed values — Negative Scenarios — (from Non-Goals) • NEG-1: Given a Cloud API company, when they open any Campaign Detail, then no Bid Information panel is rendered. |
| [WABID-S04] — WhatsApp Balance Deducted by Reserved Price As a Campaign Admin, I want my WhatsApp Balance charged the actual reserved price, not my bid, so that I don't overpay for messages that cost less than my bid. | Must Have | N/A — backend/billing | Data Fields: • reserved_price (webhook), invoice_price (Meta monthly invoice)• account_wa_balanceBefore-After Behavior: Today, balance is deducted a fixed per-message price; after this story, it's deducted by the webhook-reported reserved price, reconciled monthly vs. Meta's invoice. Depends on Bifrost's new deduction flow (Section 14, blocking). | — Happy Path — • AC-1: Given a message is delivered/read, when Meta's webhook reports the reserved price, then WA Balance is deducted by that price (not the bid). • AC-2: Given a message fails (bid < reserved price), then no deduction occurs. • AC-3: Given a month of deductions, when Meta's invoice arrives, then system reconciles total deducted vs. invoiced and flags discrepancy >5%/day (Observability Alert 2). — Error / Unhappy Path — • ERR-1: Given webhook price is later inconsistent with the invoice, then logs wa_balance_reconciliation_mismatch and applies a balance adjustment.• ERR-2: Given Bifrost's new deduction flow isn't live yet, then deduction continues on the current at-delivery model until it ships (Section 14 dependency, blocking). — Permission Model — • CAN: System-triggered; Finance/Billing admin can view balance history • CANNOT: Campaign Admin cannot directly adjust deductions • Unauthorized: N/A — backend process — UI States — • N/A — reflected read-only in WABID-S03's panels — Negative Scenarios — (from Non-Goals) • NEG-1: Given a failed message, when balance deduction runs, then no amount is ever deducted for that message. |
Dependencies: WABID-S02 → WABID-S01; WABID-S03 → WABID-S01, WABID-S02; WABID-S04 → WABID-S01, Bifrost WA Balance deduction flow (blocking)
10. Rollout
| Field | Value |
|---|---|
| Feature flag | wa_dynamic_pricing_bid_strategy — default: OFF (see Constraints) |
| Stage 1 | Internal QA accounts only (Qontak internal test accounts) — validate bid strategy math + WA Balance deduction against Meta's staging/sandbox pricing |
| Stage 2 | Closed beta — ~5 high-volume MM API accounts, manually enabled, monitored closely for balance-deduction accuracy |
| Stage 3 | All MM API accounts on request |
| GA | All MM API accounts (Cloud API accounts excluded — see Non-Goals) |
| Backward compat | Yes — accounts default to "Default" strategy (matches current fixed per-message pricing) until they opt into a different strategy |
| Migration | None required for existing campaigns; only new campaigns after the flag is ON carry a bid strategy field |
11. Observability
Key Events:
| Event Name | Trigger | Properties |
|---|---|---|
bid_strategy_selected | Campaign Admin picks a strategy on campaign creation | campaign_id, strategy, bid_amount |
campaign_sent_with_bid | Campaign dispatched to Meta with a bid amount | campaign_id, recipient_count, bid_amount, strategy |
message_delivered_priced | Meta confirms delivery/read with reserved price | message_id, campaign_id, reserved_price, bid_amount |
message_failed_low_bid | System infers a message failed due to bid < reserved price | message_id, campaign_id, bid_amount |
bid_retry_submitted | Automatic or manual retry submitted | campaign_id, new_bid_amount, retry_count, retry_type (automatic/manual), estimated_success_pct |
wa_balance_deducted | Balance deducted for a delivered/read message | account_id, message_id, reserved_price |
wa_balance_reconciliation_mismatch | Monthly invoice reconciliation finds a discrepancy | account_id, deducted_total, invoiced_total, variance_pct |
| Field | Detail |
|---|---|
| Dashboard owner | Revenue Squad |
| Alert 1 | message_failed_low_bid rate > 20% of sent in 1 hour → Slack #revenue-alerts |
| Alert 2 | wa_balance_deducted sum diverges from Meta's reported charges by > 5% in a day → PagerDuty on-call (billing accuracy risk) |
11.1. Post-Launch Monitoring Cadence
| Field | Detail |
|---|---|
| Review cadence | Weekly for the first 4 weeks post-GA, then monthly |
| Owner | Revenue Squad PM (Evelin Suwantio) |
| Review scope | message_failed_low_bid, bid_retry_submitted, wa_balance_deducted, wa_balance_reconciliation_mismatch |
| Trigger threshold 1 | message_failed_low_bid rate rises > 15% week-over-week → investigate strategy defaults immediately |
| Trigger threshold 2 | wa_balance_reconciliation_mismatch variance > 5% for 2 consecutive months → escalate to Bifrost + Finance |
| Rollback consideration | If reconciliation variance exceeds 5% and cannot be resolved within 5 business days, PM disables wa_dynamic_pricing_bid_strategy globally pending root cause. |
12. Success Metrics
Adoption & Usage:
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| ⭐ Non-Default strategy adoption | % of MM API campaigns using Cost Saver/Aggressive/Custom (not Default) | N/A — new feature | ≥25% of MM API campaigns within 30 days of GA |
| Retry usage rate | % of campaigns with low-bid failures that use the retry flow | N/A — new feature | ≥40% within 30 days of GA |
Quality & Accuracy:
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| Delivery rate parity | Delivered ÷ sent, vs. current fixed-price delivery rate | N/A — unmeasurable today | Within ±2% of current baseline within 60 days of GA |
| WA Balance deduction accuracy | Reserved-price deductions matching Meta's reported charges | N/A — new mechanism | ≥99.5% accuracy within 30 days |
Efficiency & Impact:
| Metric | Definition | Baseline | Target |
|---|---|---|---|
| Avg. cost saved per message | IDR saved via Cost Saver strategy vs. Default price | N/A — new feature | Report avg. savings ≥Rp30/message within 60 days |
| "Missing delivery" support tickets | Volume of tickets citing undelivered WA campaign messages | N/A — unmeasurable today | ≤50% reduction within 90 days of GA |
13. Launch Plan & Stage Gates
| Stage | Audience | Duration | Success Gate to Advance | Owner |
|---|---|---|---|---|
| Internal Alpha | Internal QA accounts | 1 week | 0 P0/P1 bugs; WA Balance deduction accuracy ≥99.5% in test traffic | PM + QA |
| Closed Beta | ~5 high-volume MM API accounts | 2 weeks | Delivery rate parity within ±2% of baseline; 0 balance-deduction discrepancy incidents | PM + CSM |
| Open Beta | All MM API accounts on request | 3 weeks | message_failed_low_bid rate stable (not trending up week-over-week); retry usage ≥20% | Eng Lead |
| GA | All MM API accounts | Ongoing | All Open Beta gates sustained 2 weeks; WA Balance deduction accuracy ≥99.5% sustained | PM + PMM |
14. Dependencies
| Dependency | Owning Team | Deliverable Needed | Blocking? |
|---|---|---|---|
| WA Balance deduction flow supporting Reserved Price (for Pending/Sent status) | Bifrost Squad (Addo Hernando) | New balance deduction flow — BIF PRD/1-Pager | YES |
| Meta dynamic pricing API contract confirmed | Meta (external vendor) | Answers to the open questions on reserved-price timing, price-on-failure, price volatility (Section 16) | YES |
15. Key Decisions + Alternatives Rejected
Decisions Made
| Date | Decision | Rationale |
|---|---|---|
| 2026-06-05 | Offer 4 preset bid strategies (Cost Saver, Default, Aggressive, Custom) instead of requiring manual per-campaign bidding | Reduces user education burden (Business Goal); most users lack visibility into per-recipient demand to bid manually |
| 2026-06-05 | Charge the actual Reserved Price, not the user's Bid Amount | Matches Meta's billing model; avoids overcharging when bid > reserved price |
| 2026-07-03 | Retry model: 1 automatic retry, then up to 2 manual retries (by changing bid amount), max 3 attempts total | Balances recovery opportunity against spam/cost risk while giving users enough control to succeed via bid adjustment |
| 2026-07-03 | Bid amount bounds derived from Meta's published per-country marketing-message price (min = published price, max = 2× published price), not a fixed IDR value | Reflects Meta's actual per-country pricing rather than misprice non-Indonesia markets with a fixed Rp300/Rp1,192.66 bound |
Alternatives Rejected
| Alternative | Why Rejected | Date |
|---|---|---|
| Manual per-recipient bid entry | Too much operational burden; users can't see per-recipient reserved-price demand | 2026-06-05 |
| Unlimited retries on low-bid failure | Spam/cost risk to recipients and account balance | 2026-06-05 |
| Custom-only bidding (no presets) | Doesn't reduce user education burden — defeats the stated Business Goal | 2026-06-05 |
| Fixed universal min/max bid regardless of country | Doesn't reflect Meta's per-country published pricing; would misprice for non-Indonesia markets | 2026-07-03 |
16. Open Questions
| # | Type | Question / Fact | Owner | Deadline / Mitigation |
|---|---|---|---|---|
| 1 | Assumption | Dynamic pricing applies to MM API only (Cloud API excluded) | Evelin Suwantio | Confirmed 2026-07-03 |
| 2 | Assumption | Bid amount is mandatory before sending; template campaigns default to a system value if not explicitly chosen | Evelin Suwantio | Confirmed 2026-07-03 |
| 3 | Risk | Delivered/read webhook price may differ from the final invoice due to data-processing variance (same as Meta's Analytics API) | Burhanudin Hakim | Mitigation: deduct near-real-time from webhook price; reconcile monthly against Meta's invoice (invoice = source of truth). Covered by Observability Alert 2 and event wa_balance_reconciliation_mismatch. |
| 4 | Assumption | Meta's reserved price is influenced by message volume/engagement history (exact formula proprietary, not fully replicable) | Burhanudin Hakim | Confirmed 2026-07-03 |
| 5 | Assumption | Final confirmed price charged is only available via Meta's monthly invoice (not guaranteed real-time per-message) | Burhanudin Hakim | Confirmed 2026-07-03 — ties to Risk #3 |
| 6 | Risk | Meta provides no error code for "failed — bid too low," so the system cannot directly detect this failure reason from Meta's response | Burhanudin Hakim | Mitigation: infer bid-too-low locally by comparing the submitted bid against the recipient's last known reserved price; needs engineering validation before RFC. |
| 7 | Assumption | Failed messages return no billable price info from Meta (consistent with no-charge-on-failure) | Burhanudin Hakim | Confirmed 2026-07-03 |
| 8 | Assumption | Retry model corrected: 1 automatic retry, then up to 2 manual retries (changing bid each time) = max 3 attempts total | Evelin Suwantio | Confirmed 2026-07-03 — supersedes the source doc's original "max 1 retry" |
| 9 | Assumption | No universal minimum bid — floor/ceiling derived from Meta's published per-country marketing price (Rp300 is Indonesia's value specifically) | Evelin Suwantio | Confirmed 2026-07-03 — corrects Section 5 Constraints |
Types:
Assumption·Open Question·Risk
PRD CHANGELOG
| Version | Date | By | Section | Type | Summary |
|---|---|---|---|---|---|
| 1.0 | 2026-07-03 | Claude | All | REFORMATTED | Reformatted from the legacy Confluence draft to Qontak PRD template v1.2. 9 sections extracted/mapped, 6 sections filled via coaching interview (S6, S7, S9-S13 gaps), 0 flags remaining — all residual Meta-API unknowns tracked as mitigated Risk rows in Section 16. |