What is billing reconciliation?
Billing reconciliation is the process of proving that a WhatsApp invoice is right. Engineering and finance line up three sources — your own send log, the per-message pricing data Meta reports, and the invoice from Meta or your partner — and explain every difference between them. The invoice is the document; reconciliation is the check performed on it, usually monthly and per WhatsApp Business Account, and more often during large campaigns.
The raw material is the status webhook. Each sent, delivered and read status carries the webhook pricing object, whose type (regular, free_entry_point or free_customer_service) and category (marketing, utility, authentication, authentication-international, service and so on) say whether and how that message was charged. Stored once per wamid, these become a ledger with one row per billable message, by category, country and day. The account's pricing_analytics field gives Meta's own aggregate of the same thing: delivered VOLUME and approximate COST in the account's currency, broken down by PRICING_CATEGORY, PRICING_TYPE, COUNTRY and volume TIER.
Reconciling means comparing those layers and giving every variance a reason: template recategorisation that moved a template from utility to marketing, deliveries that landed in a free entry point window, service replies that fell inside a number's monthly free tier of 1,000, a volume tier that lowered the rate mid-month, an authentication-international charge, late or duplicate webhooks, a rate change on the first day of a quarter, or a partner's markup, platform fee and GST on top of Meta's charges. When a partner's credit line pays for the account, Meta does not return COST in pricing analytics, so the partner's invoice and your own ledger carry the money side.
Key takeaways
- Billing reconciliation proves a WhatsApp invoice right by matching your send log, Meta's pricing data and the invoice line by line.
- Book one ledger line per
wamid, from the delivered or read status, only whenpricing.typeisregular. pricing_analyticsgives Meta's delivered volume and approximate cost by category, pricing type, country and tier.- Typical variances: recategorised templates, free entry point deliveries, the 1,000-a-month service tier, volume tiers, rate changes and partner fees.
- Accounts on a partner's credit line get no
COSTin pricing analytics, so the partner's invoice carries the money side.
How does billing reconciliation work?
- Step 1
Log intent at send time
For every send, store the
wamid, template name, the category you expected, the recipient's country as its own column and a cost centre or campaign tag. This is the baseline everything else is compared with. - Step 2
Book delivered pricing lines once
From status webhooks, record
pricing.typeandpricing.categorywhen a message isdeliveredorread, whichever arrives first, keyed bywamid. Apply webhook deduplication — Meta retries webhooks and sends pricing on several statuses. - Step 3
Aggregate in the invoice's terms
Group billable lines by category, country and day, in the same time zone and currency as the invoice. Volume tiers reset at 00:00 in the account's time zone, so month boundaries must match it.
- Step 4
Compare with pricing analytics
Pull
pricing_analyticsfor the same period withDAILYgranularity and the same dimensions. Volumes should agree closely once deduplication and time zones are right; costs are Meta's approximation. - Step 5
Tie out to the invoice
Apply the rate card to the billable counts — plus your partner's fees and GST if a partner bills you — and compare with the invoice total, category by category.
- Step 6
Explain and act on every variance
Give each difference a reason code, fix what is yours (recategorised templates, booking bugs, missing webhooks) and raise what is not with your partner or Meta, with the
wamidlist as evidence.
Billing reconciliation in code
-- One row per billable delivery, booked once per wamid from status webhooks.SELECTcategory, -- pricing.category, not the template's requested categorycountry, -- stored separately from the phone numberCOUNT(*) AS billable_messagesFROM wa_billing_ledgerWHERE pricing_type = 'regular'AND delivered_at >= TIMESTAMPTZ '2026-10-01 00:00+05:30' -- the account's time zoneAND delivered_at < TIMESTAMPTZ '2026-11-01 00:00+05:30'GROUP BY category, countryORDER BY category, country;
Your side of the comparison: billable deliveries for October 2026, by category and country.
Common mix-ups
Common mistakes with billing reconciliation
Summing every webhook that carries pricing
The pricing object rides on
sent,deliveredandread, and Meta retries webhooks. Book one line perwamidor your ledger will run well above the invoice.Mixing time zones at month end
Webhook timestamps are UNIX seconds, while invoices and volume tiers follow the account's time zone. Messages sent near midnight on the last day land in the wrong month unless you convert.
Reconciling against the requested category
Meta can approve a template in a different category or recategorise it later. Compare with
pricing.categoryfrom the webhook, which is what billing used.Treating analytics cost as exact — or expecting it at all
Meta describes
COSTas approximate, and it is not returned for accounts on a partner's credit line. Use it as a cross-check; the invoice is the authority.Shipping raw phone numbers to finance
Reconciliation exports need dates, countries, categories and
wamids, not full numbers. Apply PII masking before the file leaves engineering and keep country as its own column.
Example: A 12,000-message gap at a Gurugram brand
A Gurugram D2C brand's October invoice shows 412,000 marketing and 96,000 utility messages, while its campaign log expected 400,000 and 110,000. Reconciliation explains both lines. Two order-update templates had been recategorised to marketing after Meta's review, so about 12,000 sends moved from the utility column to marketing — the ledger's pricing.category showed it, the requested category did not. Another 2,000 utility messages expected by the log were never delivered, so they were never charged. The team rewrites the two templates as pure order updates and starts alerting on template category updates.
Why the difference matters
Why does billing reconciliation matter?
WhatsApp spend scales with every campaign, and small errors compound: a mis-categorised template, a webhook handler that books reads as new deliveries, or a missed rate change can move a monthly bill by lakhs without anyone noticing. Reconciliation turns the invoice from something you pay into something you can explain — and it is the evidence you need to raise a billing dispute, charge costs back to teams or clients, and trust the cost-per-message figures marketing plans with.
Billing reconciliation: key facts and limits
- Join key
- wamid
- Billable test
- pricing.type = regular on delivered or read
- Meta's aggregate
- pricing_analytics field on the WABA
- Granularity options
- HALF_HOUR, DAILY, MONTHLY
- Analytics cost
- Approximate, in the account's currency
- Partner credit line
- COST not returned — use the partner invoice
- Rate changes
- Only on the 1st of a quarter
Rules of thumb
Best practices for billing reconciliation
- Store the raw pricing object with each wamid so any report can be rebuilt later.
- Reconcile weekly during big campaigns, not only at month end.
- Keep a variance log with a reason code for every difference you explain.
- Alert when marketing's share of billable messages jumps — recategorisation is the usual cause.