WhatsApp Payments API (Brazil): Order Details, Order Status, and Why WhatsApp Never Reconciles a Single Rupee or Real
WhatsApp now lets a business send an itemized order bubble straight inside the chat and collect payment without the buyer ever leaving the app — but the API is region-locked, field-strict, and deliberately blind to whether the money actually arrived.
The WhatsApp Cloud API Payments feature turns a normal chat thread into a checkout flow. A business sends an order_details interactive message with a header image, an itemized list, and a “Review and Pay” button. The buyer pays inside WhatsApp using Pix, a payment link, Boleto, or one-click card checkout. The business then sends an order_status message back into the same thread to update the order bubble to processing, then paid. It looks like a native checkout — but the payment rail underneath is entirely separate from WhatsApp, and that separation is where most integrations get into trouble.
Region lock: this is not a global WhatsApp feature. The Payments API documented here is Brazil-specific — currency must be BRL, the amount offset must be 100, and payment_type must be br. A separate India variant exists with its own mechanics (UPI, dynamic VPA, different payment gateways). The two are not interchangeable, and a business outside these markets does not have access to either.
What the order_details message actually contains
Header
Optional image. If left out, the API defaults to the first product’s catalog image — a silent fallback that can put the wrong photo on an order bubble if a business assumes the header is required.
Body
Required text, capped at 1024 characters, supports emoji and light markdown.
Footer
Optional, capped at 60 characters.
Action
Required. Must carry the fixed name review_and_pay plus a parameters object — reference_id, currency, total_amount, and an optional itemized order object.
reference_id is the one field that quietly breaks integrations
Every order_details message needs a reference_id: letters, numbers, underscores, dashes, and dots only, capped at 60 characters, and it must be unique per message for that business. Resend a message for the same order without changing it and the two messages collide. Meta’s own guidance is to append a sequence number on any resend — a detail that is easy to skip on a first build and hard to notice until an order silently shows the wrong status because two messages are pointing at the same ID.
The math has to balance exactly
| Field | Rule |
|---|---|
| subtotal.value | Must exactly equal the sum of each item’s amount (or sale_amount) times quantity |
| total_amount.value | Must exactly equal subtotal plus tax plus shipping minus discount |
| offset | Must be 100 for BRL — a fixed-point, cents-style encoding that trips up first-time integrations |
| expiration.timestamp | Must be at least 300 seconds in the future |
| order.status at creation | Can only be pending — every other status is set later via a separate order_status message |
A mismatched subtotal or total is the kind of error that looks like a rounding bug in testing and becomes a rejected order in production.
order_status: pushing updates back into the same thread
Once the buyer pays, the business sends an order_status message to update the same order bubble — order.status moves through pending, processing, partially_shipped, shipped, completed, or canceled, and payment.status separately tracks pending, captured, or failed. Canceling an order that already has a successful or pending payment against it fails outright — the API returns a dedicated cancellation-failure error rather than silently allowing it.
The Order Details Template: the same object, wrapped in Meta’s review process
There is also a template version — order_details_template — built as a normal WhatsApp message template with a fixed ORDER_DETAILS button type. It goes through the standard template category, review, and approval flow (UTILITY or MARKETING), and the header can carry a PDF invoice via a document attachment. The underlying order fields and character limits are identical to the direct interactive message; the tradeoff is template-review turnaround in exchange for a reusable, pre-approved order format.
What WhatsApp will never do for you
Meta’s own documentation states it plainly: WhatsApp does not support payment reconciliation. The business is responsible for reconciling each payment with its own payment service provider using the reference_id from the order. WhatsApp only carries the message thread and the order_status updates — the actual money movement, settlement, and reconciliation logic live entirely outside WhatsApp, in whatever PSP integration the business builds.
Where implementations commonly stall
Assuming the Payments API is global
It is scoped to specific markets. Building against the Brazil fields for an India rollout, or vice versa, does not work — the currency, offset, and payment_type constraints are market-specific.
Getting the offset and rounding wrong
The fixed-point cents-style encoding and the exact subtotal/total match requirement reject orders that look correct at a glance.
Reusing a reference_id on resend
No sequence suffix on a resent order_details message means two messages collide on the same ID.
Building reconciliation as an afterthought
Since WhatsApp never confirms payment on its own, a business needs its PSP webhook and reference_id matching logic in place before launch, not after the first disputed order.
This sits alongside WhatsApp’s other interactive message types and the wider WhatsApp Business API / Cloud API setup support service, connects to WhatsApp interactive messages (reply buttons, lists, CTA URLs), and to the underlying WhatsApp Embedded Signup and Tech Provider onboarding flow that most Payments-enabled numbers are provisioned through.