Every subscription business loses a slice of MRR to payments that just... fail. A card expires, a bank flags a charge as suspicious, 3-D Secure times out. Stripe's dashboard shows you these failures if you go looking — but nobody goes looking until a customer emails asking why their account got downgraded.
This post walks through a tiny n8n workflow that catches every failed Stripe payment and emails you about it the moment it happens. Three nodes, no dashboard, no monthly SaaS fee.
The problem in one sentence
invoice.payment_failed and charge.failed are real-time Stripe webhook events. If nothing is listening for them, the only way you find out is by checking manually or waiting for a support ticket.
The workflow
1. Webhook node — listens for POST requests from Stripe.
2. Code node — normalizes the payload, since invoice.payment_failed and charge.failed have slightly different shapes:
const body = items[0].json.body;
const obj = body.data.object;
const isInvoice = body.type === 'invoice.payment_failed';
const email = isInvoice ? obj.customer_email : (obj.billing_details && obj.billing_details.email);
const amount = (obj.amount_due ?? obj.amount) / 100;
const currency = (obj.currency || 'usd').toUpperCase();
const declineReason = isInvoice
? (obj.last_finalization_error && obj.last_finalization_error.message) || 'Card declined'
: (obj.failure_message || 'Card declined');
return [{ json: { email, amount, currency, declineReason, eventType: body.type } }];
3. HTTP Request node — POSTs to SendGrid (or Postmark/Resend/Mailgun — same pattern) with the customer, amount, and decline reason.
That's the whole thing. Wire the Stripe webhook to point at this endpoint, subscribe to invoice.payment_failed and charge.failed, and you get a Slack-worthy alert the second a payment fails.
Setup
- Import the workflow JSON (full file at the bottom of this post, or in the n8n community thread where I first shared it).
- In Stripe Dashboard → Developers → Webhooks, point an endpoint at the Webhook node's URL.
- Add a SendGrid API key as an HTTP Header Auth credential on the HTTP Request node.
- Activate.
Where I'd take this further
This version only tells you about the failure — it doesn't do anything about it. The natural next step is a proper dunning sequence: email the customer (not just yourself), retry on a schedule, escalate over a week, and stop automatically the moment they pay. That's a longer workflow (14 nodes vs. 3), and I built a version of it as a paid template if you want the done-for-you version — but the pattern above is the free, useful-on-its-own core of it.
Happy to answer questions about the Stripe webhook shapes or the n8n setup in the comments.
Top comments (0)