# Billing webhooks

Events for the prepaid balance that pays for SMS, calls, numbers, X and the rest of pay-per-use.

## Events

| Event | Description |
| --- | --- |
| [`balance.low`](/webhooks/billing/balance-low) | The balance dropped below the low-balance alert |
| [`balance.empty`](/webhooks/billing/balance-empty) | The balance reached $0 and pay-per-use is paused |
| [`balance.charged`](/webhooks/billing/balance-charged) | A card top-up of the balance succeeded, manual or automatic |

## How it behaves

<Accordions>
<Accordion title="Balance billing only" id="balance-billing-only">

These events fire for accounts on the prepaid balance. They belong to the billing owner, not to a profile or account.

An endpoint scoped with `profileIds` or `accountIds` does not receive them. A restricted API key needs the `money` resource group.

</Accordion>
<Accordion title="Once per occurrence" id="once-per-occurrence">

- `balance.low` fires once per drop below the alert set on auto top-up, unless an auto top-up is already covering the drop.
- `balance.empty` fires once each time the balance runs out with no auto top-up covering it.
- `balance.charged` fires once per top-up.

Deduplicate retries on `id`.

</Accordion>
<Accordion title="Send the user to fundingUrl" id="send-the-user-to-fundingurl">

`balance.low` and `balance.empty` carry `fundingUrl`, the Billing page with the top-up drawer open. Pay-per-use calls answer 402 `insufficient_balance` or `usage_limit_reached` with the same link as `funding_url` until the balance is funded.

</Accordion>
<Accordion title="Amounts are in USD cents" id="amounts-are-in-usd-cents">

On `balance.charged`, `topup.amountCents` was added to the balance and the card was charged `topup.chargedCents`, which is that plus `topup.taxCents` (VAT, 0 when none applies). Stripe emails the receipt for the charge.

</Accordion>
</Accordions>

## Related

- [Webhooks](/webhooks): create an endpoint, retries, signatures.