WhaBuzzdocs
Guides

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 daythe 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 = ₹100

That 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_paise150000: credited to the wallet
gst_paise27000: 18% of the amount, rounded to the paisa
total_paise177000: 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

StateWhat happens
activeNormal. wallet.low fires once when the runway drops to 7 days or less.
graceThe balance reached zero. Sending keeps working for 7 days, the balance goes negative, and wallet.exhausted fires every day with the days left.
suspendedThe 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:

json
{
  "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

bash
curl https://api.whabuzz.com/v1/billing/wallet \
  -H "Authorization: Bearer $WHABUZZ_KEY"
json
{
  "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.

bash
curl "https://api.whabuzz.com/v1/billing/wallet/ledger?limit=2" \
  -H "Authorization: Bearer $WHABUZZ_KEY"
json
{
  "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

bash
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 }'
json
{
  "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

bash
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.

bash
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.

SituationResult
New key201 and a new top-up.
Same key, same body200 with the same top-up (and its open checkout).
Same key, different body422 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.

EventWhen
wallet.creditedA top-up was paid and credited.
wallet.debitedThe day's charge, once per day, only when it is above zero.
wallet.lowThe runway fell to 7 days or less. Once per crossing; it re-arms after a top-up.
wallet.exhaustedThe balance reached zero (grace starts), then every day of grace.
wallet.suspendedThe grace ended; sends now answer 402.
wallet.restoredThe balance is positive again after grace or suspension.

Example payloads (data):

json
// 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.