Commerce Hub: Carrier Billing & Pay Links
The checkout guide covers one purchase end to end; the rails guide covers all five rails briefly. This guide goes deep on the two rails a merchant operates daily from the console: Direct Carrier Billing (DCB) — charges that land on the customer’s mobile operator bill — and hosted pay links, the channel-agnostic fallback checkout page. Each section works the same flow through the API and the Commerce hub console pages it lands on. You will:- Pick the rail — DCB vs card checkout vs pay link
- Onboard a DCB merchant
- Run operator-billed charges
- Reconcile the operator settlement
- Drive the hosted pay-link lifecycle
X-API-Key header (or a session JWT). Use a sandbox key (dv_test_sk_…) while you build; swap in a live key (dv_live_sk_…) for production. Every rail round-trips a serializable snapshot — the API computes the next valid state; you persist the returned snapshot verbatim.
In the dashboard, open Messages → Commerce (/messages/commerce). The hub links the rail consoles this guide walks: DCB (/messages/commerce/dcb) and Pay links (/messages/commerce/pay-links), plus Subscriptions (/messages/commerce/subscriptions) for the recurring-billing sibling rail.
1. Pick the rail
The right rail depends on what the buyer can pay with in the thread you have:- DCB (operator billing) — the customer pays on their mobile carrier bill. No card, no wallet, no checkout page — the buyer confirms a code their carrier texts them and the charge appears on the phone bill. Best fit for digital goods (games, streaming, news, content passes) sold to prepaid mobile customers in EMEA/MENA, where card penetration is low and a carrier-bill line converts better than a redirect. Constrained by the merchant’s per-transaction cap and its country+operator coverage map — DCB never runs on a pair the tenant has not onboarded (
live). - Card checkout (native in-thread where it exists) — WhatsApp Pay where Meta has rolled payments out, Apple Pay inside Apple Messages for Business. The buyer pays without leaving the conversation; a card rails underneath. Use it whenever the channel offers a native wallet and the buyer is card-holding.
- Hosted pay link (fallback) — when no native wallet exists (SMS, email, Telegram, Viber, a voice-IVR readout with a QR code), mint a tokenized hosted checkout URL and render it into the thread. It also opens cleanly inside WhatsApp’s in-app browser. The pay link is universal — use it as the fallback when DCB coverage or a native wallet is missing, and as the recovery leg when a DCB charge is declined (
failed).
live DCB pair under the tenant’s merchant config, bill the operator; otherwise mint a pay link. When a DCB charge fails mid-flight, route the same buyer to a pay link instead of retrying the operator — an operator decline is terminal.
2. Onboard a DCB merchant
Onboarding declares the merchant’s settlement currency, digital-goods category, per-transaction charge cap, and tenant-level coverage map of country+operator pairs:live. pilot pairs are visible but blocked while the operator agreement finishes; unsupported is an explicit off switch. There is no global country gate — a tenant onboarded for SA/stc bills there and nowhere else. maxChargeAmount is the per-charge ceiling in major currency units; a charge above it never reaches the operator.
In the console (/messages/commerce/dcb), the merchant card mirrors this call: set the merchant name, settlement currency, category, and per-transaction cap, then build the coverage list — the picker lists common EMEA/MENA pairs (e.g. SA/stc, AE/etisalat) as hints. Saving the card calls POST /commerce/dcb/merchant and keeps the returned snapshot in view, so the next section’s charge form reuses it.
3. Run operator-billed charges
Initiate a charge against the onboarded merchant. The API validates, in order: a positive amount, the currency matching the merchant’s, the amount under the cap, and the target country+operator pair beinglive:
capturedAmount (a partial capture the operator confirmed) is honoured; it can never exceed the initiated amount. After capture, refund part or all of the captured value (/dcb/charge/refund — full headroom lands the charge in terminal refunded, a partial one in partially_refunded), and record an operator-initiated reversal with /dcb/charge/chargeback. refunded, charged_back, and failed are terminal: no transition leaves them, and they settle for zero.
In the console (/messages/commerce/dcb), the charge form and its lifecycle buttons drive the same calls: initiate a charge with a masked buyer number, build the operator request, then Capture / Fail / Refund / Chargeback the selected charge. Each charge pill shows its lifecycle state (initiated → captured/failed → partially_refunded/refunded/charged_back) and the refunded-vs-captured amounts, so a half-refunded charge reads at a glance.
4. Reconcile the operator settlement
Operators settle in batches — a settlement file lines each charge id up with the amount the operator actually pays out. Close the batch by reconciling your charges against those lines:- matched — settled amount and currency equal the expected net; the charge moves to settled.
- amount_mismatch / currency_mismatch — the operator settled a different figure or a different currency than expected.
- missing — a captured charge with positive net has no settlement line (the operator did not pay it out).
- unexpected — a settlement line with no matching charge in the batch.
reconciled: true only when every captured charge matched and nothing unexpected arrived. Refunded, charged-back, failed, and not-yet-captured charges net to zero and are not expected in the settlement, so they never pollute the missing set. Totals are re-derived server-side — a forged settlement total cannot pass.
In the console (/messages/commerce/dcb), paste the operator’s settlement lines into the reconciliation card and run it: the result panel shows the matched/mismatched/missing/unexpected counts, the gross-to-net figures with the signed delta, and per-charge lines with the reason each did (not) reconcile. Matched charges stamp settled in the list; a non-zero delta or any missing line is the signal to dispute the batch with the operator before the next cycle.
5. Hosted pay-link lifecycle
When DCB is not the fit, the hosted pay link is the channel-agnostic checkout. Mint one — amount, currency, the hosted checkout base URL, an optional description, your own reconciliation reference, and an optional TTL (expiresAt, epoch seconds):
whatsapp, rcs, sms, email, telegram, viber, or voice (the pay-by-voice IVR readout plus a QR payload):
MARK_VIEWED when the buyer opens the page, MARK_PAID on payment, EXPIRE once the TTL passes, CANCEL to withdraw a minted link (/payment-request/transition — an illegal transition, including any move after a terminal state, rejects with VALIDATION_ERROR). Every real status change fires the commerce.payment_request.status_changed webhook to your subscribed endpoints, so an order-management system learns the outcome without polling.
When the PSP reports the capture, close the request against the captured amount and currency:
paid; on a mismatch the response returns the unchanged snapshot with the mismatch reason so you can dispute the capture. A request whose expiresAt has passed accepts only EXPIRE — mint a fresh link; an expired one never comes back.
In the console (/messages/commerce/pay-links), the mint form maps one-to-one to the create call (amount, currency, hosted base URL, description, reference, TTL in minutes). The link panel then shows the minted URL with copy/open actions, renders the request for a picked channel, and drives the lifecycle with Mark viewed / Mark paid / Expire / Cancel buttons. The status pill tracks pending → viewed → paid (or expired / cancelled), and the reconciliation card takes the PSP’s captured amount, currency, and reference — an exact match flips the link to paid in place, with the history keeping the terminal snapshot.
Subscriptions. The recurring sibling rail — a subscription reuses the mandate the customer already signed, so a renewal collects no new consent — lives at /messages/commerce/subscriptions; the rails guide walks its API. Watch the next-charge dates and the renewal ledger there while operating DCB and pay links.
See also
- Conversational Commerce Checkout — the end-to-end purchase thread these rails close, §6 names the rails this guide operates
- Conversational Commerce Rails — the five-rail overview with one worked snippet each
- Commerce API reference — every endpoint and field
- Checkout spine concept — why one cart resolves across surfaces