Prepaid wallet
How the prepaid wallet works: daily charges, top-ups with GST, the 7-day grace, the 402 wallet_suspended answer, the routes and the wallet.* events.
WhaBuzz bills from a prepaid wallet. You add money with a top-up, and WhaBuzz takes a small amount from the balance every day for each connected number. There is no per-message fee. Meta's own charges go to your Meta account, not to the wallet (see Pricing).
Every /v1/billing route answers 503 billing_disabled while billing is switched off for the platform. Nothing is deducted until it is on.
#The numbers
| First connected number | ₹500 per month |
| Each additional connected number | ₹250 per month |
| One day | the monthly price ÷ 30 |
All balances are whole paise and exclude GST: 150000 is ₹1,500.00.
The daily run happens at 02:30 IST. It charges every number that is connected; a number that is pending or disconnected is not charged. Sandbox accounts, trials, accounts billed through a connected app, and accounts WhaBuzz has waived are not charged (billed: false on the wallet, with the reason in not_billed_reason).
Worked example. Two connected numbers for four days:
4 days × (₹500 + ₹250) ÷ 30 = ₹100That is ₹25.00 a day, so daily_burn_paise is 2500 and a balance of ₹1,845.00 has a runway_days of 73.
#GST and top-ups
A top-up credits the amount you choose. 18% GST is added on top and you pay the total:
amount_paise | 150000: credited to the wallet |
gst_paise | 27000: 18% of the amount, rounded to the paisa |
total_paise | 177000: what you pay |
A tax invoice from the same WB/<financial year>/ series is issued when the payment is confirmed. With a GSTIN from another state it shows IGST; otherwise CGST and SGST. Set your GSTIN on Billing > Settings or with PATCH /v1/tenant.
The amount can be from 50000 (₹500, one month of one number) to 50000000 (₹5,00,000) paise. Below the minimum the answer is 400 amount_below_minimum with details.min_paise (50000). You can top up from the day you sign up, including during the trial: the credit waits and is used once daily charges start (see Trial).
The balance is non-refundable. It is only returned, by hand, if you close the account.
#When the wallet runs out
| State | What happens |
|---|---|
active | Normal. wallet.low fires once when the runway drops to 7 days or less. |
grace | The balance reached zero. Sending keeps working for 7 days, the balance goes negative, and wallet.exhausted fires every day with the days left. |
suspended | The 7 days passed. Your numbers show service_state: "suspended_billing" and sends answer 402 wallet_suspended. |
While suspended, inbound messages and webhooks keep flowing, and so do reads and mark-as-read. Only sending stops. A top-up that brings the balance above zero restores everything at once (wallet.restored). No daily charge is taken while suspended. The negative balance (the days used in grace) stays, and a top-up pays it off first, so a top-up smaller than that balance does not restore sending.
#The 402
POST /v1/messages and POST /v1/messages/batch refuse before anything is queued:
{
"source": "whabuzz",
"error": {
"code": "wallet_suspended",
"message": "Sending is paused: the prepaid wallet ran out and the 7-day grace period has ended. Top up the wallet to resume.",
"details": { "top_up": "/v1/billing/wallet/top-ups" }
},
"request_id": "req_01JAB41B6E9G2J5M8P1S4V7Y0B"
}Do not retry a 402 wallet_suspended. Top up, then send again.
#Routes
Reads need any access to the tenant. Starting a top-up needs an owner or admin session, or a key that can manage billing; anyone else gets 403.
#Read the wallet
curl https://api.whabuzz.com/v1/billing/wallet \
-H "Authorization: Bearer $WHABUZZ_KEY"{
"currency": "INR",
"balance_paise": 184500,
"state": "active",
"billed": true,
"not_billed_reason": null,
"billable_numbers": 2,
"monthly_paise": 75000,
"daily_burn_paise": 2500,
"runway_days": 73,
"rates": { "first_number_monthly_paise": 50000, "additional_number_monthly_paise": 25000 },
"grace": null,
"last_top_up": { "id": "wtu_01J9ZK3Q8M2V5N7R4T6X0YABCD", "amount_paise": 150000, "total_paise": 177000, "paid_at": "2026-10-17T10:02:00Z", "invoice_number": "WB/2026-27/000001" },
"next_deduction_at": "2026-10-22T21:00:00.000Z"
}runway_days is null when nothing is being charged. In grace, grace is { "started_at", "ends_at", "days_left" }. next_deduction_at is null until daily charges have begun.
#The ledger
Newest first. limit is 1 to 200 (default 50). Pass the next_cursor of one page as cursor for the next; it is null on the last page.
curl "https://api.whabuzz.com/v1/billing/wallet/ledger?limit=2" \
-H "Authorization: Bearer $WHABUZZ_KEY"{
"data": [
{ "id": "wl_01J9ZK3Q8M2V5N7R4T6X0YABCE", "kind": "debit", "amount_paise": 2500, "balance_after_paise": 184500, "currency": "INR", "ref_kind": "day", "ref_id": "2026-10-22", "exempt": false, "detail": { "numbers": 2 }, "created_at": "2026-10-22T21:00:04Z" },
{ "id": "wl_01J9ZK3Q8M2V5N7R4T6X0YABCF", "kind": "credit", "amount_paise": 150000, "balance_after_paise": 187000, "currency": "INR", "ref_kind": "payment", "ref_id": "wtu_01J9ZK3Q8M2V5N7R4T6X0YABCD", "exempt": false, "detail": null, "created_at": "2026-10-17T10:02:00Z" }
],
"next_cursor": "wl_01J9ZK3Q8M2V5N7R4T6X0YABCF"
}kind is credit, debit or adjustment. amount_paise is always positive; the kind gives the direction.
#Start a top-up
curl -X POST https://api.whabuzz.com/v1/billing/wallet/top-ups \
-H "Authorization: Bearer $WHABUZZ_KEY" \
-H "Idempotency-Key: topup-2026-10-22-a" \
-H "Content-Type: application/json" \
-d '{ "amount_paise": 150000 }'{
"id": "wtu_01J9ZK3Q8M2V5N7R4T6X0YABCD",
"state": "pending",
"currency": "INR",
"amount_paise": 150000,
"gst_paise": 27000,
"total_paise": 177000,
"checkout_url": "https://pay.example/c/ord_1",
"expires_at": "2026-10-22T11:00:00Z",
"invoice_number": null,
"failure_reason": null,
"created_at": "2026-10-22T10:30:00Z",
"paid_at": null
}Send the person to checkout_url (UPI, card or netbanking). When they finish, the payment page returns them to ${CONSOLE_ORIGIN}/billing?top_up=<id>; the wallet is credited when the payment is confirmed, usually within a minute.
The body takes amount_paise (whole paise) and an optional phone, the payer's 10-digit mobile for the receipt. It is asked for once and saved as your billing phone; you also need a billing email on the account. Without them the answer is 400 invalid_request.
#List and read top-ups
curl "https://api.whabuzz.com/v1/billing/wallet/top-ups?limit=25" -H "Authorization: Bearer $WHABUZZ_KEY"
curl https://api.whabuzz.com/v1/billing/wallet/top-ups/wtu_01J9ZK3Q8M2V5N7R4T6X0YABCD -H "Authorization: Bearer $WHABUZZ_KEY"The list answers { "data": [ … ] }, newest first, with the same fields as above. state is pending, paid, failed or expired; checkout_url is only present while pending. Poll the single top-up after a payment until it leaves pending.
#The tax invoice
Ask the same route for HTML. It answers 409 conflict until the top-up is paid.
curl https://api.whabuzz.com/v1/billing/wallet/top-ups/wtu_01J9ZK3Q8M2V5N7R4T6X0YABCD \
-H "Authorization: Bearer $WHABUZZ_KEY" -H "Accept: text/html" -o invoice.html#Idempotency
Send an Idempotency-Key (1 to 255 characters) when you start a top-up so a retry never opens a second checkout.
| Situation | Result |
|---|---|
| New key | 201 and a new top-up. |
| Same key, same body | 200 with the same top-up (and its open checkout). |
| Same key, different body | 422 idempotency_conflict. Use a new key. |
The same rule applies to the top-up's checkout: it is created once per top-up, so replays return the same checkout_url.
#Events
The wallet raises six events. Subscribe to wallet.* types by name or with *. Each is deduplicated, so a retry never repeats one.
| Event | When |
|---|---|
wallet.credited | A top-up was paid and credited. |
wallet.debited | The day's charge, once per day, only when it is above zero. |
wallet.low | The runway fell to 7 days or less. Once per crossing; it re-arms after a top-up. |
wallet.exhausted | The balance reached zero (grace starts), then every day of grace. |
wallet.suspended | The grace ended; sends now answer 402. |
wallet.restored | The balance is positive again after grace or suspension. |
Example payloads (data):
// wallet.credited
{ "amount_paise": 150000, "gst_paise": 27000, "total_paise": 177000, "balance_paise": 334500, "currency": "INR", "top_up_id": "wtu_01J9ZK3Q8M2V5N7R4T6X0YABCD", "invoice_number": "WB/2026-27/000001" }
// wallet.debited
{ "amount_paise": 2500, "balance_paise": 184500, "currency": "INR", "day": "2026-10-22", "numbers": 2 }
// wallet.low
{ "balance_paise": 12500, "currency": "INR", "runway_days": 5, "daily_burn_paise": 2500, "threshold_days": 7 }
// wallet.exhausted
{ "balance_paise": -3000, "currency": "INR", "grace_ends_at": "2026-10-27T21:00:00.000Z", "grace_days_left": 5 }
// wallet.suspended
{ "balance_paise": -20000, "currency": "INR", "grace_started_at": "2026-10-20T00:00:00.000Z" }
// wallet.restored
{ "balance_paise": 98000, "currency": "INR", "previous_state": "suspended" }#In the console
Billing > Wallet shows the balance, runway, daily charge and next deduction, the top-up form with the GST line before you pay, and the ledger. Owners and admins can top up; other members see the balance only. Top-ups and their tax invoices are listed on Billing > Top-ups.
This page as Markdown: /guides/billing.md. All docs in one file: /llms-full.txt. Status.