subscription.payment_recovery ​
Handles failed payment recovery (dunning process)
Handles failed payment recovery (dunning).
States: initiated → notified → awaiting_retry ↔ retrying → payment_method_updated → recovered (terminal/success) → subscription_canceled (terminal/fail) → failed (terminal/fail)
Key features: - Tracks Stripe's automatic retry attempts - Supports payment method updates from customer - Sends customer notification on failure detection - Records final outcome: recovered or subscription canceled
Overview ​
| Property | Value |
|---|---|
| Workflow type | Linear |
| Library | App-subscription |
| Version | 2.0 |
Triggers ​
| Source | Endpoint / Event | Description |
|---|---|---|
| WEBHOOK | invoice.payment_failed | Stripe webhook when payment fails — starts the workflow |
| WEBHOOK | invoice.paid | Stripe webhook when payment succeeds — call action='payment_succeeded' |
| WEBHOOK | customer.subscription.deleted | Stripe webhook when subscription is canceled — call action='subscription_canceled' |
| WEBHOOK | payment_method.updated | Stripe webhook when payment method updated — call action='payment_method_updated' |
Input Schema ​
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
subscription_uuid | uuid | Yes | — | OrganizationSubscription UUID |
organization_uuid | uuid | Yes | — | Organization UUID |
stripe_subscription_ref | string | Yes | — | Stripe subscription reference |
stripe_invoice_ref | string | Yes | — | Failed invoice reference |
stripe_customer_ref | string | Yes | — | Stripe customer reference |
amount_due | integer | Yes | — | Amount due in cents |
currency | string | No | — | Currency code (default: usd) |
failure_code | string | No | — | Stripe failure code |
failure_message | string | No | — | Human-readable failure message |
attempt_count | integer | No | — | Current attempt count |
next_retry_at | string | No | — | Next retry timestamp (ISO) |
Output Schema ​
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
subscription_uuid | uuid | No | — | — |
organization_uuid | uuid | No | — | — |
stripe_subscription_ref | string | No | — | — |
stripe_invoice_ref | string | No | — | — |
stripe_customer_ref | string | No | — | — |
amount_due | integer | No | — | — |
currency | string | No | — | — |
failure_code | string | No | — | — |
failure_message | string | No | — | — |
attempt_count | integer | No | — | — |
next_retry_at | string | No | — | — |
initiated_at | string | No | — | — |
notification_sent | boolean | No | — | — |
notification_sent_at | string | No | — | — |
notification_type | string | No | — | — |
awaiting_retry_since | string | No | — | — |
last_retry_at | string | No | — | — |
retry_invoice_ref | string | No | — | — |
last_failure_code | string | No | — | — |
last_failure_message | string | No | — | — |
last_retry_failed_at | string | No | — | — |
payment_method_updated_at | string | No | — | — |
new_payment_method_ref | string | No | — | — |
new_payment_method_type | string | No | — | — |
new_payment_method_last4 | string | No | — | — |
recovered_at | string | No | — | ISO timestamp of successful recovery |
final_amount_paid | integer | No | — | — |
final_payment_intent_ref | string | No | — | — |
total_attempts | integer | No | — | — |
keys_expiry_bumped | json | No | — | — |
canceled_at | string | No | — | ISO timestamp the subscription was canceled for non-payment |
cancellation_reason | string | No | — | — |
total_failed_attempts | integer | No | — | — |
failed_at | string | No | — | — |
failure_reason | string | No | — | — |
failure_type | string | No | — | — |
failed_action | string | No | — | — |
failed_at_state | string | No | — | — |
failed_step | string | No | — | — |
failed_layer | string | No | — | — |
error | string | No | — | — |
error_type | string | No | — | — |
States ​
| State | Initial | Terminal | Success | Auto-advance | Description |
|---|---|---|---|---|---|
initiated | Yes | No | — | send_notification | Payment failure detected |
awaiting_retry | No | No | — | — | Waiting for Stripe automatic retry |
notified | No | No | — | — | Customer notified of failed payment |
payment_method_updated | No | No | — | — | Customer updated payment method |
retrying | No | No | — | — | Stripe is retrying the payment |
failed | No | Yes | No | — | Workflow failed |
recovered | No | Yes | Yes | — | Payment recovered successfully |
subscription_canceled | No | Yes | No | — | Subscription canceled due to non-payment |
State Diagram ​
Transitions ​
| From | Action | To | Description |
|---|---|---|---|
initiated | send_notification | notified | Send failure notification |
notified | schedule_retry | awaiting_retry | Wait for Stripe retry |
awaiting_retry | retry_started | retrying | Retry in progress |
retrying | retry_failed | awaiting_retry | Retry failed, wait again |
notified | payment_method_updated | payment_method_updated | PM updated from notified |
awaiting_retry | payment_method_updated | payment_method_updated | PM updated from awaiting |
retrying | payment_method_updated | payment_method_updated | PM updated from retrying |
initiated | payment_succeeded | recovered | Quick recovery from initiated |
notified | payment_succeeded | recovered | Recovery from notified |
awaiting_retry | payment_succeeded | recovered | Recovery from awaiting |
retrying | payment_succeeded | recovered | Recovery from retrying |
payment_method_updated | payment_succeeded | recovered | Recovery after PM update |
initiated | subscription_canceled | subscription_canceled | Cancel from initiated |
notified | subscription_canceled | subscription_canceled | Cancel from notified |
awaiting_retry | subscription_canceled | subscription_canceled | Cancel from awaiting |
retrying | subscription_canceled | subscription_canceled | Cancel from retrying |
payment_method_updated | subscription_canceled | subscription_canceled | Cancel from PM updated |
* (any state) | fail | failed | Catch-all failure handler |
Outcomes ​
| Outcome | Type | Description | State Data Keys |
|---|---|---|---|
recovered | SUCCESS | Payment succeeded after recovery | recovered_at, final_amount_paid, total_attempts |
subscription_canceled | FAILURE | Subscription canceled due to non-payment | canceled_at, cancellation_reason, total_failed_attempts |
failed | FAILURE | Workflow error — see failure_reason | failed_at, failure_reason |
Alarms ​
| Name | Type | State | Severity | Description |
|---|---|---|---|---|
subscription_canceled_non_payment | state_entered | subscription_canceled | error | Subscription canceled due to non-payment |
recovery_stuck | state_timeout | awaiting_retry | warning | Payment recovery stuck for 7 days |
API Usage ​
bash
# Step 1: Start the workflow
POST /api/workflows/start
Content-Type: application/json
{
"workflow_type": "subscription.payment_recovery",
"initial_data": {
"subscription_uuid": "value",
"organization_uuid": "value",
"stripe_subscription_ref": "value",
"stripe_invoice_ref": "value"
}
}
# Step 2: Webhook triggers transition (invoice.payment_failed)
POST /api/workflows/{workflow_id}/transition
Content-Type: application/json
{ "action": "...", ... webhook payload ... }