> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.paymentkit.com/guides/billing/dunning-recovery/dunning-profiles/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paymentkit.com/_mcp/server. # Dunning profiles # Overview Dunning profiles let you customize payment recovery behavior beyond the default settings. Each profile defines: * **Retry schedule**: How many retries and how often * **Failure handling**: What happens when all retries fail * **Email notifications**: Which emails to send at each retry step > **Note** > > Profiles are applied at the start of a dunning cycle and remain locked for that cycle. Changes to a profile don't affect in-flight dunning cycles. --- # Profile types PaymentKit supports two types of dunning profiles with fundamentally different retry strategies: | Type | Control | Intelligent Retries | Max Retries Ceiling | | ------------ | ------------------- | --------------------------------- | ------------------- | | **Adaptive** | System-managed | Yes (technical, liquidity, final) | 12 | | **Custom** | Merchant-controlled | No (fixed intervals only) | 15 | > **Note** > > **Mutual exclusivity**: A subscription uses either an adaptive or custom profile, never both. If multiple profiles match, custom profile assignments take precedence. --- # Adaptive profiles (system defaults) Adaptive profiles are system-managed and use intelligent retry scheduling that responds to decline types: | Profile | Target Cycle | Max Retries | Retry Schedule | Best For | | ----------------------------------- | ------------ | ----------- | --------------------------------- | --------------------- | | **Daily - Quick Recovery** | Daily | 2 | 12 hours after failure | Daily subscriptions | | **Short Cycle - Standard Recovery** | 2-6 days | 3 | Day 1, day 2 | Weekly subscriptions | | **Monthly - Standard Recovery** | 7-29 days | 7 | Days 2, 5, 10, 15, 20, 27 | Monthly subscriptions | | **Long Cycle - Extended Recovery** | 30+ days | 9 | Days 2, 5, 10, 15, 20, 30, 35, 50 | Annual subscriptions | > **Note** > > Adaptive profiles use **increasing retry spacing** to reduce the chance of an issuer > blocking repeated attempts. The gaps are returned as `retry_interval_schedule_hours` on > the profile — hours between attempts, applied in order. `retry_interval_hours` is still > returned for compatibility but is not used when a schedule is present. > > Retries are always cut short by the renewal wall, and by a 30-day maximum dunning > window, so a long-cycle profile stops well before day 50 in practice. ## Intelligent retry behaviors Adaptive profiles automatically inject additional retry attempts based on decline type: | Retry Type | Decline Type | When Applied | | --------------------- | -------------------------------------------- | ------------------------------------ | | **Technical retries** | TECHNICAL (network timeout, processor error) | T+1h, T+2h after failure | | **Liquidity retries** | SOFT (insufficient funds) | 1st/15th of month or Fridays at 9 AM | > **Tip** > > The 12-retry ceiling for adaptive profiles reserves capacity for these system-injected retries, ensuring you never exceed the network's 15-retry-per-card limit. The **renewal wall** — 1 hour before the billing cycle ends — is not one of these behaviors. It applies to every profile, adaptive and custom alike. See [The renewal wall](#the-renewal-wall) below. System defaults cannot be modified or deleted, but you can clone them to create custom variations. --- # Custom profiles Custom profiles give you full control over retry behavior with fixed, predictable intervals. Create custom profiles when you need deterministic retry schedules or have specific compliance requirements. **What custom profiles provide:** * Full 15-retry ceiling (the network maximum) * Fixed `retry_interval_hours` for all retry attempts * Consistent, predictable timing between retries **What custom profiles don't include:** * No technical retries (TECHNICAL declines use standard interval) * No liquidity retries (SOFT declines use standard interval) > **Note** > > Custom profiles treat all decline types identically, using your configured `retry_interval_hours` for every retry attempt. ## Create a profile **`cURL`** ```bash cURL curl -X POST https://app.paymentkit.com/api/{account_id}/dunning/profiles \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "name": "Premium Customers", "description": "Extended recovery for high-value subscriptions", "max_retries": 12, "retry_interval_hours": 72, "termination_action": "leave_active", "invoice_status_on_failure": "leave_open", "enable_emails": true, "email_map": [ {"retry_step": 0, "template": "payment_failed"}, {"retry_step": 3, "template": "payment_reminder"}, {"retry_step": -1, "template": "final_warning"} ] }' ``` ### Profile settings | Field | Type | Default | Description | | --------------------------- | ------- | ---------------------- | -------------------------------------------------------------------------------------------- | | `name` | string | required | Display name (max 100 chars) | | `description` | string | null | Optional description (max 500 chars) | | `max_retries` | integer | 8 | Number of retry attempts (1-15) | | `retry_interval_hours` | integer | 96 | Hours between retries (1-672) | | `termination_action` | string | `"cancel"` | `"cancel"` or `"leave_active"`. What happens to the subscription when retries are exhausted. | | `invoice_status_on_failure` | string | `"mark_uncollectible"` | `"mark_uncollectible"` or `"leave_open"` | | `enable_emails` | boolean | true | Whether to send dunning emails | | `email_map` | array | null | Email templates for specific retry steps | > **Note** > > `termination_action` and `invoice_status_on_failure` take effect together, and only on > **custom** profiles. A profile you create controls what happens when its retries are > exhausted: `termination_action` decides the subscription (`"cancel"` cancels it, > `"leave_active"` leaves it past due), and `invoice_status_on_failure` decides the > invoice. The values are captured when a dunning cycle starts, so editing a profile > mid-cycle applies to the next cycle, not the one already running. > > The four built-in system profiles do not override your account-level dunning settings — > assign a custom profile when you want per-cycle-length control. ### Email map The `email_map` array controls which email templates are sent at each retry step: ```json { "email_map": [ {"retry_step": 0, "template": "payment_failed"}, // First retry {"retry_step": 2, "template": "payment_reminder"}, // Third retry {"retry_step": -1, "template": "final_warning"} // Final retry (special value) ] } ``` Use `retry_step: -1` to target the final retry, regardless of how many retries are configured. ## Clone a profile Create a copy of an existing profile (including system defaults): > **Warning** > > A clone is a **custom** profile, so it retries on a fixed `retry_interval_hours` rather > than on the increasing schedule its source used. Cloning **Monthly - Standard Recovery** > gives you a profile that retries every 96 hours, not on days 2, 5, 10, 15, 20 and 27. > Clones also do not get the technical or liquidity retries described above. Assign the > system default itself if you want that behavior. **`cURL`** ```bash cURL curl -X POST https://app.paymentkit.com/api/{account_id}/dunning/profiles/{profile_id}/clone \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"name": "My Custom Monthly Profile"}' ``` ## Archive a profile Archiving soft-deletes a profile. Archived profiles don't appear in the default list but remain accessible by ID. In-flight dunning cycles using the profile continue unaffected. **`cURL`** ```bash cURL curl -X DELETE https://app.paymentkit.com/api/{account_id}/dunning/profiles/{profile_id} \ -H "Authorization: Bearer sk_live_..." ``` --- # The renewal wall Every dunning cycle for a subscription invoice ends at the **renewal wall**: 1 hour before the current billing period ends. The wall keeps a cycle's retries from stacking into the next billing period. The wall is a system constraint and **cannot be overridden by profile configuration**. It applies identically to adaptive and custom profiles: * **No retry is ever scheduled at or after the wall.** * **The last attempt lands on the wall.** If your configured interval would place the next retry past the wall, that attempt is pulled back to the wall and runs there as the final retry. * **Once the wall has passed, dunning ends** and your configured failure handling is applied. > **Note** > > The wall clamps your schedule; it does not extend it. If your profile's retries are already exhausted before the wall is reached, dunning ends there — no extra attempt is added just because the wall is still ahead. You never get more retries than your configured `max_retries`. > **Tip** > > Setting a `retry_interval_hours` longer than the billing cycle means most of your configured retries never run: the cycle reaches the wall first, and the attempt that would have crossed it becomes the final one. Keep the interval well under the cycle length if you want the full retry budget to be used. One-time invoices have no next billing period and therefore no renewal wall. Their retries are bounded by the one-time retry window instead. --- # Profile assignments By default, PaymentKit selects a system default profile based on the subscription's billing cycle. Use **profile assignments** to override this for specific prices or billing cycle categories. ## Resolution order When a dunning cycle starts, PaymentKit resolves the profile using a 3-tier priority: 1. **Price assignment** — If the subscription's price has a profile assigned, use that profile 2. **Cycle-length assignment** — If no price assignment, check for a cycle\_length category assignment 3. **System default** — Fall back to the system default for the billing cycle type ```mermaid flowchart TD A[Dunning starts] --> B{Price has assignment?} B -->|Yes| C[Use price-assigned profile] B -->|No| D{Cycle-length has assignment?} D -->|Yes| E[Use cycle-length profile] D -->|No| F[Use system default] ``` ## Assign to a price Target a specific product price for custom dunning behavior: **`cURL`** ```bash cURL curl -X POST https://app.paymentkit.com/api/{account_id}/dunning/profiles/{profile_id}/assignments \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "resource_type": "price", "resource_id": "price_prod_abc123" }' ``` ## Assign to a cycle length Target all subscriptions with a specific billing cycle category: **`cURL`** ```bash cURL curl -X POST https://app.paymentkit.com/api/{account_id}/dunning/profiles/{profile_id}/assignments \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "resource_type": "cycle_length", "resource_id": "standard" }' ``` Valid cycle lengths: `daily`, `short`, `standard`, `long` | Cycle Length | Billing Period | | ------------ | -------------- | | `daily` | 1 day | | `short` | 2-6 days | | `standard` | 7-29 days | | `long` | 30+ days | ## List assignments **`cURL`** ```bash cURL curl https://app.paymentkit.com/api/{account_id}/dunning/profiles/{profile_id}/assignments \ -H "Authorization: Bearer sk_live_..." ``` ## Delete an assignment **`cURL`** ```bash cURL curl -X DELETE https://app.paymentkit.com/api/{account_id}/dunning/profiles/{profile_id}/assignments/{assignment_id} \ -H "Authorization: Bearer sk_live_..." ``` --- # Profile snapshots When a dunning cycle starts, PaymentKit captures a **snapshot** of the assigned profile's settings. This snapshot is immutable for the duration of the dunning cycle. > **Note** > > Changes to a profile (including archiving) don't affect dunning cycles already in progress. Each cycle uses its frozen snapshot. This ensures predictable behavior: * Updating `max_retries` from 8 to 5 won't suddenly cut short an in-progress cycle * Archiving a profile won't disrupt active dunning cycles using it * New dunning cycles always get the latest profile settings The snapshot is stored in the dunning state and includes all profile fields: ```json { "profile_snapshot": { "schema_version": 2, "profile_id": "dp_prod_abc123", "profile_name": "Premium Customers", "max_retries": 12, "retry_interval_hours": 72, "termination_action": "leave_active", "invoice_status_on_failure": "leave_open", "enable_emails": true, "email_map": [...], "is_system_default": false } } ``` --- # Best practices #### Start with system defaults System defaults are optimized for common billing cycles. Clone them as a starting point for custom profiles. #### Use price assignments for exceptions Assign profiles to specific prices (e.g., annual enterprise plans) rather than creating many cycle-length assignments. #### Test with short retry intervals When testing, create a profile with `retry_interval_hours: 1` to see the full dunning cycle quickly. #### Don't over-customize Most merchants need 1-3 profiles. Complex assignment hierarchies are harder to reason about. > Customize retry schedules and failure handling for different subscription types with dunning profiles.