Automatic retries & emails

PaymentKit’s dunning system automatically recovers failed payments through retries and emails while giving you control over subscription and invoice outcomes when all payment attempts fail.

View as Markdown

How dunning works

Dunning is composed of three coordinated actions:

1

Payment retries

Automatic attempts to charge the customer.

2

Email notifications

Up to 5 emails per dunning cycle (capped at one every 24 hours), each containing a secure payment method update link. Emails are sent at the retry steps configured in the profile’s email map — not necessarily after every attempt.

3

Failure handling

Controls what happens if all payment attempts ultimately fail:

  • Subscription status: Cancel subscription / pause subscription / leave subscription active (past due) 
  • Invoice status: Mark invoice as uncollectible / leave invoice open

Retry schedules

PaymentKit automatically retries failed payments according to the subscription billing cycle. The schedule varies by cycle length.

  • Retry interval: Time between retry attempts
  • Final retry: Last retry before applying failure handling
  • Retry attempts: floor(final_retry / retry_interval) + 1, then capped by the profile’s max retries (adaptive defaults: 3 daily, 4 short, 8 standard, 10 long)

Cycle lengths & parameters

Cycle lengthRetry intervalFinal retry (offset from the invoice due date)
Daily (1 day)23 hoursmin(23h, payment_terms*24h - 1h, next_invoice_date - invoice_due_date - 1h)
Short (2–6 days)2 days (48 hours)min(cycle_length - 1, payment_terms - 1, next_invoice_date - invoice_due_date - 1)
Standard (7–29 days)4 days (96 hours)min(cycle_length - 1, max_dunning_window, payment_terms - 1, next_invoice_date - invoice_due_date - 1)
Long (≥30 days)4 days (96 hours)min(cycle_length - 1, max_dunning_window, payment_terms - 1, next_invoice_date - invoice_due_date - 1)

max_dunning_window defaults to 30 days. Standard and long cycles share the same interval and final-retry formula; they differ only in the adaptive profile’s default max retries (8 vs 10).

Emails are sent at the retry steps configured in the profile’s email map — typically the first at the initial failure and the last at the final retry, with additional steps for longer cycles — capped at 5 per cycle and no more than one every 24 hours. The timeline below is illustrative; emails do not necessarily follow every retry.

Intervals adjust for cycle type; Short and daily cycles would compress accordingly.


Retry behavior by profile type

How retries are scheduled depends on whether you’re using an adaptive (system default) or custom dunning profile.

Adaptive profiles: Intelligent retries

Adaptive profiles inject additional retry attempts based on the decline reason:

Decline TypeExtra RetriesTiming
TECHNICAL (network timeout, processor error)Technical retriesT+1h and T+2h after failure
SOFT (insufficient funds)Liquidity retries1st/15th of month or Fridays at 9 AM

Liquidity retries target paydays when customers are most likely to have funds available. This significantly improves recovery rates for insufficient funds declines.

These intelligent retries don’t run alongside the standard schedule — they’re reconciled with it through a 24-hour deduplication window. When more than one retry falls in the same window, the highest-priority one fires (priority order: final > technical > liquidity > standard).

Custom profiles: Fixed intervals

Custom profiles use your configured retry_interval_hours for every retriable decline, with no decline-type-specific timing:

  • TECHNICAL declines → fixed interval (no T+1h/T+2h fast-path)
  • SOFT declines → fixed interval (no liquidity/payday targeting)
  • HARD declines → payment-method update, the same as adaptive profiles (the fallback-card cascade is not profile-specific)

Retries occur at predictable, fixed intervals.

Choose custom profiles when you need deterministic retry schedules, have compliance requirements for fixed timing, or want full control over every retry attempt.

The renewal wall applies to both

The renewal wall — 1 hour before the current billing period ends — is not an adaptive behavior. It is a system constraint that profile configuration cannot override, and it binds custom profiles exactly as it binds adaptive ones:

  • No retry is scheduled at or after the wall.
  • If your fixed interval would place the next retry past the wall, that attempt is pulled back onto the wall and runs there as the final retry. Choosing a custom profile does not forfeit the last attempt of the cycle.
  • Once the wall has passed, dunning ends and your configured failure handling is applied.

The wall clamps your schedule rather than extending it. If your configured retries run out before the wall, dunning ends there — no extra attempt is added beyond your max_retries.

A retry_interval_hours close to or longer than the billing cycle means most of your configured retries never run: the cycle hits the wall first and the attempt that would have crossed it becomes the final one. Keep the interval well under the cycle length to use the full retry budget.

One-time invoices have no next billing period and therefore no renewal wall; their retries are bounded by the one-time retry window instead.


Failure handling settings

When retries are off or exhausted, PaymentKit applies these rules:

TypeOptionDescription
Subscription statusCancel subscriptionCancel the subscription when all retries fail
Pause subscriptionPause the subscription when all retries fail
Keep subscription active (past-due)Leave the subscription active even if all payments fail
Invoice statusMark as uncollectibleTreat the invoice as uncollectible after all retries fail
Leave invoice openKeep the invoice open for future collection attempts

The invoice status choice takes effect only when the subscription is paused. When the subscription is cancelled or left active (past-due), the invoice is always marked uncollectible.

For most businesses, cancelling the subscription and marking the invoice uncollectible will give you the cleanest reporting.


Hard decline handling

When a payment method returns a hard decline (expired card, closed account, invalid card number, stolen/lost card), PaymentKit:

  1. Marks the payment method as declined for this specific invoice
  2. Attempts the next payment method in the customer’s payment method list
  3. Repeats until success or all payment methods have hard declined

If all payment methods hard decline, the invoice moves to “Update payment method” status and:

  • Automatic retries stop — no usable payment method remains to charge
  • The customer is notified to update their payment method
  • Adding a valid payment method resumes collection; if the dunning cycle has already ended, the new method starts a fresh cycle

Hard decline tracking is per-invoice. A payment method that fails on one invoice may still work for other invoices.


Edge cases during dunning

PaymentKit gracefully handles scenarios where external changes occur during active dunning:

Invoice manually paid

If an invoice is marked as paid outside the normal payment flow (e.g., manual payment recorded, payment via alternative channel), dunning stops immediately:

  • No further automatic retries are scheduled
  • Dunning status transitions to “Completed”
  • Subscription state updates based on remaining unpaid invoices

Invoice voided

When an invoice is voided during dunning:

  • Dunning ends with the terminal status “Invoice voided” (distinct from “Completed”)
  • The subscription continues if other invoices are still active
  • No end behavior is applied (voiding is explicit cancellation of the debt)

Subscription cancelled externally

If a subscription is cancelled while in dunning (e.g., via API, admin action, or customer request):

  • Scheduled payment retries are skipped
  • Outstanding invoices remain in their current state
  • End behavior for existing invoices follows your failure handling settings

Payment method changes

During active dunning:

  • Method deleted: Next retry uses remaining methods; if none available, transitions to “Update payment method”
  • Method added: New method is included in the next retry attempt
  • Default method changed: New default is prioritized in the retry order