Dunning
Dunning is what happens when an invoice is not paid. It is the sequence of retries and reminders that runs after the due date passes, so that most late payments resolve themselves before anybody opens a spreadsheet.
A subscription being chased keeps its status. It is still ACTIVE; how far the
chase has got is recorded in its billing state, not in its status and not in
a phase. Filtering subscriptions by status will not find it.
What starts and ends it
An invoice becomes overdue when its due date passes with a balance
outstanding. That is the trigger.
From there the dunning policy decides, step by step and counted in days after the due date, when to email the customer, when to retry the saved payment method, and when to escalate: a grace stage, suspension, and finally cancellation. It ends in one of three ways: the invoice is paid and the subscription recovers, the sequence reaches its terminal step, or somebody intervenes, for example by writing the invoice off.
While any invoice of the subscription is overdue, its next cuts are held: no further invoice is written until the overdue ones are paid.
Dunning is billing state, not a status or a phase
SubscriptionStatus is DRAFT, ACTIVE, PAUSED or CANCELLED. A subscription
in dunning is usually ACTIVE, and billing_state.dunning_stage on
GET /v1/subscriptions/{id} says how far through the sequence it has got:
WARNED, GRACED or SUSPENDED. See Subscriptions.
How a policy is built
Dunning is configured per organisation, and a plan can carry its own policy for the subscriptions on it. Both are set over the API, as described in Set it up with the API.
Each step runs a number of days after the due date and does one thing: send an email, retry the saved payment method, grant grace, suspend, or cancel. For consumer customers, minimum waiting times between steps are enforced.
Each email step sends mail. The wording lives in your email templates, so the policy decides when and the template decides what.
Set it up with the API
PUT /v1/dunning-policy creates the organisation's policy (201) or replaces the
one it has (200). PUT /v1/plans/{planId}/dunning-policy does the same for a
plan-level policy, which overrides the organisation's for subscriptions on that
plan.
Remind, retry, then escalate
The rules a policy must meet:
actionissend_email,retry_payment,grace,suspendorcancel.days_after_dueis at least 1 and strictly increases from step to step.- A
send_emailstep needsemail_event. It is a label of your choosing and selects no template: every email step sends the same dunning email, and the label comes back asemail_eventon thedunning.step.emailwebhook event so you can tell the steps apart. cancelis terminal: no step may follow it.- A
retry_paymentstep charges the saved payment method and is not a notice, so it carries nonotice.
For consumer customers the minimums are: first notice no earlier than 7 days after the due date, notices at least 7 days apart, and no suspension or cancellation before day 30. Retry steps do not count towards them. A consumer is not chased on a policy that breaks them.
GET /v1/dunning-policy returns the policy that applies to the organisation, its
own or one shared by an organisation above it (provenance says which); data
is null when there is neither. GET /v1/plans/{planId}/dunning-policy returns
a plan's override, or null when the plan follows the organisation's.
DELETE /v1/dunning-policy and DELETE /v1/plans/{planId}/dunning-policy remove
a policy.
Watch it from the API
Invoice state and subscription billing state are public, so you observe dunning through them and through webhook events:
billing_state is present on GET /v1/subscriptions/{id} and absent on lists
and writes. dunning_stage and dunning_since are absent when no dunning
episode stands. On the invoice, last_dunning_step_index and
last_dunning_step_at record the most recent step that ran.
Events
| Event | When |
|---|---|
subscription.dunning_entered | The subscription enters dunning (stage WARNED) |
subscription.dunning_stage_changed | Dunning escalates (WARNED, GRACED, SUSPENDED) |
subscription.recovered | The subscription recovers after payment clears |
dunning.step.email, dunning.step.grace, dunning.step.suspend, dunning.step.cancel | A step of that kind fires |
invoice.payment_attempt_failed | A single gateway payment attempt fails (retryable) |
invoice.payment_failed_terminal | Payment retries are exhausted |
Dunning cases
GET /v1/dunning-cases lists something different: money a customer owes
outside any invoice. A case opens when a dispute you lost could not take back
money from the customer's wallet because the wallet had already spent it. The
dunning.case.opened, dunning.case.closed and dunning.case.reopened events
track them.
Terms and API names
| Term | In the API |
|---|---|
| an invoice past its due date | status: "overdue" |
| a subscription being chased | billing_state.dunning_stage is set |
| how far through the sequence | billing_state.dunning_stage: WARNED, GRACED, SUSPENDED |
| since when | billing_state.dunning_since |
| the organisation's policy, a plan's policy | PUT /v1/dunning-policy, PUT /v1/plans/{planId}/dunning-policy |
Where dunning goes next
- A payment arrives and the subscription recovers
(
subscription.recovered). - Nothing arrives and the sequence ends in suspension or cancellation, according to your policy.
- You write it off with
POST /v1/invoices/{id}/write-off, which declares an overdue invoice uncollectible (uncollectible) without reversing it, or you reduce what is owed with a credit note.
Next step
Continue to Webhooks to react to payment failures in your own system as they happen.
Reference
| Topic | When you need it |
|---|---|
| Subscriptions | Status, and where billing state is read |
| Payments | Why a payment failed in the first place |
| Credit notes | Reducing what is owed |
| Invoices API | Invoice status, the public signal for overdue |