How to build a Stripe failed-payment workflow in n8n
n8n suits failed-payment recovery: it can wait for days, call Stripe over HTTP and send email from your own server. This guide walks through the workflow node by node.
1. Catch the event, and prove it came from Stripe
Start with a Webhook node on POST and point a Stripe webhook endpoint at it for invoice.payment_failed. Stripe signs every event. Turn on the raw body in the Webhook node, compute an HMAC-SHA256 of timestamp.rawBody with your whsec_ secret using the Crypto node, and compare it to the v1 value in the Stripe-Signature header. Reject anything older than five minutes. Without this step, anyone who finds the URL can make your workflow email your customers.
Add an idempotency check straight after: Stripe can send the same event twice, so record the event id and drop repeats.
2. Fetch the real decline code
The event does not say why the card failed. Use an HTTP Request node with your Stripe credential. On older API versions the invoice has a payment_intent id, so you call GET /v1/payment_intents/{id}. On 2025-03-31.basil and later that field is gone: call GET /v1/invoice_payments?invoice={id}&limit=1&expand[]=data.payment.payment_intent instead (why). Then read last_payment_error.decline_code. The built-in Stripe node does not cover payment intents, so HTTP Request is the way. Strip the ids down to letters, digits and underscores before putting them in a URL.
3. Branch by reason with a Switch node
- Hard declines such as
expired_card,lost_card,stolen_card: never retry. Go straight to the email branch. insufficient_funds: retry near payday, the 1st or the 15th, not on a fixed timer.- Technical errors such as
processing_error: retry within hours. - Everything else: treat as a soft decline, retry a few days apart, and email in parallel.
The exact code groups are in the Stripe decline codes guide.
4. Retry with Wait nodes, inside the network limits
Loop through a Wait node, then an HTTP Request to POST /v1/invoices/{id}/pay. Keep a counter: Visa allows 15 retries in 30 days, Mastercard 10 in 24 hours, and going over can get your account flagged. n8n keeps a waiting execution alive across restarts, so a five-day wait is fine.
5. Email with a fresh card-update link
For each email, create a Stripe customer portal session with POST /v1/billing_portal/sessions and put that URL in the button. Portal links expire, so make a new one per email, never reuse the first. Send through the Send Email node with your own SMTP, from your own name and address. Five short emails over about two weeks, softer at the start and clearer at the end, is the pattern in the dunning email guide.
6. Re-check before every retry and every email
Right before each send, fetch the invoice. If its status is paid, void or uncollectible, stop and record why. This is the step most homemade workflows skip, and it is the one that stops you emailing a customer who already paid.
7. Record the outcome
End every case in one place, recovered, lost or closed, with the decline code and how it came back. Even a row in a Google Sheet is enough to see after a month which reasons cost you the most.
A free starting point
Steps 1 to 3, verifying the event, fetching the decline code and branching by reason, are a free n8n template on GitHub under the MIT licence. Import it and hang your own retries and emails off each branch. If you would rather have all seven steps built and tested, plus PayPal, the expiring-card warning and a dashboard, RevGuard Stripe Recovery is the finished version for $79 once, running on your own n8n.