Stripe Failed Payment Dunning Workflow
Triggered by a Stripe failed-invoice/charge event via webhook, the flow looks up the customer, sends a branded Outlook dunning email with a hosted retry link, logs the failure and retry attempt to Dataverse, and runs a staged retry schedule (day 1/3/7). Escalates to an account manager and flags churn risk after the final failure.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
The Stripe Failed Payment Dunning Workflow runs an automated, event-driven dunning (failed-payment recovery) process on top of Stripe. It is delivered as two cloud flows + one Dataverse ledger table in a single solution: 1. Receiver — a real-time HTTP webhook that Stripe calls on invoice.payment_failed / charge.failed. It looks up the Stripe customer, opens a dunning ledger record, and sends the customer the first branded recovery email containing the Stripe hosted-invoice retry link. 2. Retry Sweep — a daily scheduled flow that walks the open ledger records, re-checks Stripe to see if the invoice was paid, sends staged reminders on a configurable cadence (default day 1 / 3 / 7), marks recovered invoices, and escalates exhausted cases to the account manager as churn risk. A shared correlationId is minted in the receiver and stamped on the ledger row so the entire recovery lifecycle is traceable across both flows. Why it matters: Involuntary churn from expired cards and transient declines is recoverable revenue. A polite, staged dunning sequence with a one-click retry link recovers a large share of failed payments automatically, and only pulls a human in once automation is exhausted.
Use Case
A subscription business wants failed payments handled gracefully: the customer gets a clear, branded nudge with an easy one-click fix; finance keeps a complete ledger of failures, attempts and outcomes; and account managers are only engaged when automated recovery has run its full course. The split between a webhook receiver (real-time first contact) and a scheduled sweep (staged follow-ups) avoids long-running flows with multi-day Delay actions and makes the retry cadence fully data-driven.
Flow Architecture
When a Stripe Payment Fails (Receiver)
Request — HTTP webhookStripe posts the invoice.payment_failed / charge.failed event JSON to the flow URL.
Initialize correlation id + config
Initialize VariableMint @guid() correlation id and load company name, retry cadence (1,3,7) and dunning table name from env vars.
Compose Invoice Fields
ComposeExtract invoice id, customer id, amount, currency and hosted URL from the event.
Get Stripe Customer
Stripe — GetCustomerResolve authoritative customer email and display name; pick best of event vs customer record.
List Existing Dunning
Microsoft Dataverse — ListRecordsLook up any existing ledger record for this invoice to de-dupe.
New failure branch
Microsoft Dataverse — CreateRecord + Office 365 Outlook — SendEmailV2On a new failure, open the ledger at attempt 1, schedule first follow-up, and send the first branded dunning email.
Respond To Stripe
Response (HTTP 200)Acknowledge delivery back to Stripe.
Run Daily Retry Sweep (Sweep)
Recurrence (Day/1, 09:00)Daily sweep of open ledger records.
List Due Dunning Records
Microsoft Dataverse — ListRecordsFilter flowlibs_status eq 'Active' and flowlibs_nextretrydate le utcNow.
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_StripeApiKey | String | sk_live_REPLACE_ME | Bearer key for Stripe REST invoice read. |
| flowlibs_StripeApiBaseUrl | String | https://api.stripe.com/v1 | Stripe REST base URL. |
| flowlibs_CompanyName | String | Contoso | Brand name shown in dunning emails. |
| flowlibs_MaxRetryAttempts | String | 5 | Max attempts shown in reminders. |
| flowlibs_DunningTable | String | flowlibs_dunning | Ledger table logical name. |
| flowlibs_AccountManagerEmail | String | am@contoso.com | Final-failure churn escalation recipient. |
| flowlibs_RetryCadenceDays | String | 1,3,7 | Day offsets for staged reminders. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Stripe | shared_stripe | GetCustomer |
| HTTP | http | GET /v1/invoices/{id} |
| Microsoft Dataverse | shared_commondataserviceforapps | ListRecords CreateRecord UpdateRecord |
| Office 365 Outlook | shared_office365 | SendEmailV2 |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Retry cadence
- Edit flowlibs_RetryCadenceDays (e.g. 1,3,5,7,10); the number of entries automatically becomes the number of follow-up reminders before escalation, no flow edits needed.
- Let Stripe drive timing
- Alternatively, enable Stripe Billing's own Smart Retries and reduce this solution to comms + logging only.
- Dunning tone
- Escalate the email copy across stages (friendly to firm to final notice); the staged email already shows 'reminder X of Y'.
- Escalation routing
- Point flowlibs_AccountManagerEmail at a shared mailbox or per-segment owner.
- Signature verification
- Add an inline HMAC-SHA256 check of Stripe-Signature before processing (see Auth note).
- Going live
- Authorize the three connections, set env var values, register the Stripe webhook, and turn both flows On.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Hosted invoice retry link (receiver)
Pull the Stripe hosted-invoice retry URL from the composed invoice fields.
EXPR.02First follow-up date
Schedule the first reminder at firstFailure + cadence[0].
EXPR.03Next staged retry date (sweep)
Advance the next-retry date using the cadence array indexed by attempt count.
EXPR.04Final-attempt gate
Escalate as churn risk once attempts reach the number of cadence entries.
EXPR.05Recovered check
Treat the invoice as recovered when Stripe reports it paid.
Customize & download
Generate a ready-to-import copy of this solution with your environment-variable values baked in — available on Base, Pro, or Team.
Upgrade to customize
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.