Skip to content
Proud to collaborate with Microsoft for Startups

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 ​

PropertyValue
Workflow typeLinear
LibraryApp-subscription
Version2.0

Triggers ​

SourceEndpoint / EventDescription
WEBHOOKinvoice.payment_failedStripe webhook when payment fails — starts the workflow
WEBHOOKinvoice.paidStripe webhook when payment succeeds — call action='payment_succeeded'
WEBHOOKcustomer.subscription.deletedStripe webhook when subscription is canceled — call action='subscription_canceled'
WEBHOOKpayment_method.updatedStripe webhook when payment method updated — call action='payment_method_updated'

Input Schema ​

FieldTypeRequiredDefaultDescription
subscription_uuiduuidYes—OrganizationSubscription UUID
organization_uuiduuidYes—Organization UUID
stripe_subscription_refstringYes—Stripe subscription reference
stripe_invoice_refstringYes—Failed invoice reference
stripe_customer_refstringYes—Stripe customer reference
amount_dueintegerYes—Amount due in cents
currencystringNo—Currency code (default: usd)
failure_codestringNo—Stripe failure code
failure_messagestringNo—Human-readable failure message
attempt_countintegerNo—Current attempt count
next_retry_atstringNo—Next retry timestamp (ISO)

Output Schema ​

FieldTypeRequiredDefaultDescription
subscription_uuiduuidNo——
organization_uuiduuidNo——
stripe_subscription_refstringNo——
stripe_invoice_refstringNo——
stripe_customer_refstringNo——
amount_dueintegerNo——
currencystringNo——
failure_codestringNo——
failure_messagestringNo——
attempt_countintegerNo——
next_retry_atstringNo——
initiated_atstringNo——
notification_sentbooleanNo——
notification_sent_atstringNo——
notification_typestringNo——
awaiting_retry_sincestringNo——
last_retry_atstringNo——
retry_invoice_refstringNo——
last_failure_codestringNo——
last_failure_messagestringNo——
last_retry_failed_atstringNo——
payment_method_updated_atstringNo——
new_payment_method_refstringNo——
new_payment_method_typestringNo——
new_payment_method_last4stringNo——
recovered_atstringNo—ISO timestamp of successful recovery
final_amount_paidintegerNo——
final_payment_intent_refstringNo——
total_attemptsintegerNo——
keys_expiry_bumpedjsonNo——
canceled_atstringNo—ISO timestamp the subscription was canceled for non-payment
cancellation_reasonstringNo——
total_failed_attemptsintegerNo——
failed_atstringNo——
failure_reasonstringNo——
failure_typestringNo——
failed_actionstringNo——
failed_at_statestringNo——
failed_stepstringNo——
failed_layerstringNo——
errorstringNo——
error_typestringNo——

States ​

StateInitialTerminalSuccessAuto-advanceDescription
initiatedYesNo—send_notificationPayment failure detected
awaiting_retryNoNo——Waiting for Stripe automatic retry
notifiedNoNo——Customer notified of failed payment
payment_method_updatedNoNo——Customer updated payment method
retryingNoNo——Stripe is retrying the payment
failedNoYesNo—Workflow failed
recoveredNoYesYes—Payment recovered successfully
subscription_canceledNoYesNo—Subscription canceled due to non-payment

State Diagram ​

Transitions ​

FromActionToDescription
initiatedsend_notificationnotifiedSend failure notification
notifiedschedule_retryawaiting_retryWait for Stripe retry
awaiting_retryretry_startedretryingRetry in progress
retryingretry_failedawaiting_retryRetry failed, wait again
notifiedpayment_method_updatedpayment_method_updatedPM updated from notified
awaiting_retrypayment_method_updatedpayment_method_updatedPM updated from awaiting
retryingpayment_method_updatedpayment_method_updatedPM updated from retrying
initiatedpayment_succeededrecoveredQuick recovery from initiated
notifiedpayment_succeededrecoveredRecovery from notified
awaiting_retrypayment_succeededrecoveredRecovery from awaiting
retryingpayment_succeededrecoveredRecovery from retrying
payment_method_updatedpayment_succeededrecoveredRecovery after PM update
initiatedsubscription_canceledsubscription_canceledCancel from initiated
notifiedsubscription_canceledsubscription_canceledCancel from notified
awaiting_retrysubscription_canceledsubscription_canceledCancel from awaiting
retryingsubscription_canceledsubscription_canceledCancel from retrying
payment_method_updatedsubscription_canceledsubscription_canceledCancel from PM updated
* (any state)failfailedCatch-all failure handler

Outcomes ​

OutcomeTypeDescriptionState Data Keys
recoveredSUCCESSPayment succeeded after recoveryrecovered_at, final_amount_paid, total_attempts
subscription_canceledFAILURESubscription canceled due to non-paymentcanceled_at, cancellation_reason, total_failed_attempts
failedFAILUREWorkflow error — see failure_reasonfailed_at, failure_reason

Alarms ​

NameTypeStateSeverityDescription
subscription_canceled_non_paymentstate_enteredsubscription_cancelederrorSubscription canceled due to non-payment
recovery_stuckstate_timeoutawaiting_retrywarningPayment 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 ... }