Skip to content

Dunning

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.

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_failed
FieldTypeDescription
iduuidUnique campaign identifier
organization_iduuidOrganization that owns this campaign
codestringUnique campaign code (1–255 chars)
namestringDisplay name (1–255 chars)
descriptionstring?Optional description
max_attemptsintegerMaximum retry attempts (min: 1, default: 3)
days_between_attemptsintegerDays between retries (min: 1, default: 3)
bcc_emailsarrayEmail addresses to BCC on dunning notifications
statusstringactive or inactive (default: active)
thresholdsarrayCurrency-specific minimum amounts (see below)
created_atdatetimeCreation timestamp
updated_atdatetimeLast update timestamp

Thresholds define the minimum overdue amount per currency before the campaign creates a payment request. This prevents chasing trivially small amounts.

FieldTypeDescription
iduuidUnique threshold identifier
dunning_campaign_iduuidParent campaign ID
currencystring3-letter ISO currency code
amount_centsstringMinimum threshold in cents
created_atdatetimeCreation timestamp
updated_atdatetimeLast update timestamp
Terminal window
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": ["[email protected]"],
"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": ["[email protected]"],
"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.

Terminal window
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.

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.

Terminal window
curl -X PUT /v1/dunning_campaigns/{campaign_id} \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "inactive"}'
Terminal window
curl -X DELETE /v1/dunning_campaigns/{campaign_id} \
-H "Authorization: Bearer $API_KEY"

Returns 204 No Content on success.

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.

Terminal window
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"
}
]
}
]
}

View the payment requests created by a campaign and their current status.

Terminal window
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).

Get a chronological timeline of all events for a campaign — creation, payment attempts, successes, and failures.

Terminal window
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
}
]
}

Get aggregate recovery metrics across all campaigns.

Terminal window
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"
}

Dunning campaigns create payment requests to group overdue invoices for collection. You can also create payment requests manually or in batch.

Terminal window
# Create a manual payment request
curl -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"]
}'
Terminal window
# Batch create for all overdue customers
curl -X POST /v1/payment_requests/batch \
-H "Authorization: Bearer $API_KEY"
Terminal window
# View payment attempt history
curl /v1/payment_requests/{request_id}/attempts \
-H "Authorization: Bearer $API_KEY"

BoxBilling fires webhook events for payment request lifecycle changes. Subscribe to these in your webhook configuration.

EventTrigger
payment_request.createdPayment request created (by campaign or manually)
payment_request.payment_succeededPayment collected successfully
payment_request.payment_failedPayment failed after all retry attempts
MethodPathDescription
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_statsGet performance stats
GET/v1/dunning_campaigns/{campaign_id}/execution_historyGet execution history
POST/v1/dunning_campaigns/{campaign_id}/previewPreview campaign execution
GET/v1/dunning_campaigns/{campaign_id}/timelineGet campaign timeline
POST/v1/payment_requests/Create a payment request
POST/v1/payment_requests/batchBatch 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}/attemptsGet payment attempt history