Dunning
Overview
Section titled “Overview”Dunning automates the recovery of failed payments. When invoices go overdue, a dunning campaign groups them into payment requests and retries collection on a schedule you define. Each campaign has configurable retry limits, timing, and per-currency thresholds that control which overdue invoices qualify for automated collection.
How dunning works
Section titled “How dunning works”Overdue invoices detected │ ▼ Threshold check (per currency) │ ▼ Payment request created (groups invoices by customer + currency) │ ▼ Collection attempt ──► Success ──► payment_request.payment_succeeded │ ▼ (failed) Wait N days │ ▼ Retry attempt ──► Success ──► payment_request.payment_succeeded │ ▼ (max attempts reached) payment_request.payment_failedCampaign properties
Section titled “Campaign properties”| Field | Type | Description |
|---|---|---|
id | uuid | Unique campaign identifier |
organization_id | uuid | Organization that owns this campaign |
code | string | Unique campaign code (1–255 chars) |
name | string | Display name (1–255 chars) |
description | string? | Optional description |
max_attempts | integer | Maximum retry attempts (min: 1, default: 3) |
days_between_attempts | integer | Days between retries (min: 1, default: 3) |
bcc_emails | array | Email addresses to BCC on dunning notifications |
status | string | active or inactive (default: active) |
thresholds | array | Currency-specific minimum amounts (see below) |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
Threshold properties
Section titled “Threshold properties”Thresholds define the minimum overdue amount per currency before the campaign creates a payment request. This prevents chasing trivially small amounts.
| Field | Type | Description |
|---|---|---|
id | uuid | Unique threshold identifier |
dunning_campaign_id | uuid | Parent campaign ID |
currency | string | 3-letter ISO currency code |
amount_cents | string | Minimum threshold in cents |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
Create a dunning campaign
Section titled “Create a dunning campaign”curl -X POST /v1/dunning_campaigns/ \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "standard_recovery", "name": "Standard Recovery", "description": "Default dunning campaign for overdue invoices", "max_attempts": 3, "days_between_attempts": 5, "bcc_emails": ["collections@acme.com"], "status": "active", "thresholds": [ {"currency": "USD", "amount_cents": 500}, {"currency": "EUR", "amount_cents": 500} ] }'Response:
{ "id": "550e8400-e29b-41d4-a716-446655440000", "organization_id": "org-uuid", "code": "standard_recovery", "name": "Standard Recovery", "description": "Default dunning campaign for overdue invoices", "max_attempts": 3, "days_between_attempts": 5, "bcc_emails": ["collections@acme.com"], "status": "active", "thresholds": [ { "id": "threshold-uuid-1", "dunning_campaign_id": "550e8400-e29b-41d4-a716-446655440000", "currency": "USD", "amount_cents": "500", "created_at": "2026-03-08T10:00:00Z", "updated_at": "2026-03-08T10:00:00Z" }, { "id": "threshold-uuid-2", "dunning_campaign_id": "550e8400-e29b-41d4-a716-446655440000", "currency": "EUR", "amount_cents": "500", "created_at": "2026-03-08T10:00:00Z", "updated_at": "2026-03-08T10:00:00Z" } ], "created_at": "2026-03-08T10:00:00Z", "updated_at": "2026-03-08T10:00:00Z"}The code must be unique across all campaigns. Thresholds are optional — without them, all overdue invoices regardless of amount are eligible for collection.
Update a campaign
Section titled “Update a campaign”curl -X PUT /v1/dunning_campaigns/{campaign_id} \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "max_attempts": 5, "days_between_attempts": 7, "thresholds": [ {"currency": "USD", "amount_cents": 1000} ] }'All update fields are optional — only the fields you include are modified. Updating thresholds replaces all existing thresholds.
Deactivate a campaign
Section titled “Deactivate a campaign”Set status to inactive to pause a campaign without deleting it. Existing in-progress payment requests continue to completion, but no new requests are created.
curl -X PUT /v1/dunning_campaigns/{campaign_id} \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"status": "inactive"}'Delete a campaign
Section titled “Delete a campaign”curl -X DELETE /v1/dunning_campaigns/{campaign_id} \ -H "Authorization: Bearer $API_KEY"Returns 204 No Content on success.
Preview campaign execution
Section titled “Preview campaign execution”Simulate what the campaign would do if it ran now, without creating any payment requests. Use this to validate thresholds and understand which customers would be affected.
curl -X POST /v1/dunning_campaigns/{campaign_id}/preview \ -H "Authorization: Bearer $API_KEY"Response:
{ "campaign_id": "campaign-uuid", "campaign_name": "Standard Recovery", "status": "active", "total_overdue_invoices": 12, "total_overdue_amount_cents": "245000", "payment_requests_to_create": 5, "existing_pending_requests": 2, "groups": [ { "customer_id": "customer-uuid", "customer_name": "Acme Corp", "currency": "USD", "total_outstanding_cents": "75000", "matching_threshold_cents": "500", "invoice_count": 3, "invoices": [ { "id": "invoice-uuid", "invoice_number": "INV-2026-0042", "amount_cents": "25000", "currency": "USD", "status": "overdue" } ] } ]}Execution history
Section titled “Execution history”View the payment requests created by a campaign and their current status.
curl /v1/dunning_campaigns/{campaign_id}/execution_history \ -H "Authorization: Bearer $API_KEY"Response:
[ { "id": "payment-request-uuid", "customer_id": "customer-uuid", "customer_name": "Acme Corp", "amount_cents": "75000", "amount_currency": "USD", "payment_status": "succeeded", "payment_attempts": 2, "ready_for_payment_processing": false, "invoices": [ { "id": "invoice-uuid", "invoice_number": "INV-2026-0042", "amount_cents": "25000", "currency": "USD", "status": "paid" } ], "created_at": "2026-03-01T10:00:00Z", "updated_at": "2026-03-06T14:30:00Z" }]Supports pagination with skip and limit query parameters (default: 100 items, max: 1000).
Campaign timeline
Section titled “Campaign timeline”Get a chronological timeline of all events for a campaign — creation, payment attempts, successes, and failures.
curl /v1/dunning_campaigns/{campaign_id}/timeline \ -H "Authorization: Bearer $API_KEY"Response:
{ "events": [ { "event_type": "payment_request.created", "timestamp": "2026-03-01T10:00:00Z", "description": "Payment request created for Acme Corp", "payment_request_id": "request-uuid", "customer_name": "Acme Corp", "amount_cents": "75000", "amount_currency": "USD", "payment_status": "pending", "attempt_number": 1 }, { "event_type": "payment_request.payment_succeeded", "timestamp": "2026-03-06T14:30:00Z", "description": "Payment collected on attempt 2", "payment_request_id": "request-uuid", "customer_name": "Acme Corp", "amount_cents": "75000", "amount_currency": "USD", "payment_status": "succeeded", "attempt_number": 2 } ]}Performance stats
Section titled “Performance stats”Get aggregate recovery metrics across all campaigns.
curl /v1/dunning_campaigns/performance_stats \ -H "Authorization: Bearer $API_KEY"Response:
{ "total_campaigns": 3, "active_campaigns": 2, "total_payment_requests": 150, "succeeded_requests": 112, "failed_requests": 23, "pending_requests": 15, "recovery_rate": 74.67, "total_recovered_amount_cents": "1250000", "total_outstanding_amount_cents": "425000"}Payment requests
Section titled “Payment requests”Dunning campaigns create payment requests to group overdue invoices for collection. You can also create payment requests manually or in batch.
# Create a manual payment requestcurl -X POST /v1/payment_requests/ \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "customer-uuid", "invoice_ids": ["invoice-uuid-1", "invoice-uuid-2"] }'# Batch create for all overdue customerscurl -X POST /v1/payment_requests/batch \ -H "Authorization: Bearer $API_KEY"# View payment attempt historycurl /v1/payment_requests/{request_id}/attempts \ -H "Authorization: Bearer $API_KEY"Webhooks
Section titled “Webhooks”BoxBilling fires webhook events for payment request lifecycle changes. Subscribe to these in your webhook configuration.
| Event | Trigger |
|---|---|
payment_request.created | Payment request created (by campaign or manually) |
payment_request.payment_succeeded | Payment collected successfully |
payment_request.payment_failed | Payment failed after all retry attempts |
API endpoints
Section titled “API endpoints”| Method | Path | Description |
|---|---|---|
POST | /v1/dunning_campaigns/ | Create a dunning campaign |
GET | /v1/dunning_campaigns/ | List dunning campaigns |
GET | /v1/dunning_campaigns/{campaign_id} | Get a dunning campaign |
PUT | /v1/dunning_campaigns/{campaign_id} | Update a dunning campaign |
DELETE | /v1/dunning_campaigns/{campaign_id} | Delete a dunning campaign |
GET | /v1/dunning_campaigns/performance_stats | Get performance stats |
GET | /v1/dunning_campaigns/{campaign_id}/execution_history | Get execution history |
POST | /v1/dunning_campaigns/{campaign_id}/preview | Preview campaign execution |
GET | /v1/dunning_campaigns/{campaign_id}/timeline | Get campaign timeline |
POST | /v1/payment_requests/ | Create a payment request |
POST | /v1/payment_requests/batch | Batch create payment requests |
GET | /v1/payment_requests/ | List payment requests |
GET | /v1/payment_requests/{request_id} | Get a payment request |
GET | /v1/payment_requests/{request_id}/attempts | Get payment attempt history |