n8n Stripe Webhooks: Handle Payment Events, Subscriptions, and Refunds Automatically
Stripe fires webhooks for every meaningful payment event: a charge succeeds, a subscription renews, a payment fails, a refund is issued. If your n8n workflow isn't listening to these, you're missing real-time signals you need to run your business.
This guide covers everything you need to build a production-ready Stripe webhook handler in n8n — from wiring up the endpoint to handling the five events that matter most.
What You'll Learn
- How to create a Stripe webhook endpoint in n8n
- How to verify the webhook signature (so only Stripe can trigger your workflow)
- How to handle the five most important Stripe events
- Three real workflow patterns: payment receipt, failed payment recovery, and subscription lifecycle
Prerequisites
- n8n (self-hosted or cloud) with a publicly accessible URL
- A Stripe account with API access
- Stripe secret key and webhook signing secret (from Stripe Dashboard → Developers → Webhooks)
Step 1: Create the Webhook Trigger in n8n
- Open n8n and create a new workflow
- Add a Webhook node as the trigger
- Set HTTP Method to
POST - Copy the generated webhook URL — you'll add it to Stripe next
- Set Response Mode to
Immediately(Stripe expects a 200 within 30 seconds)
Important: If you're running n8n locally, Stripe can't reach
localhost. Use ngrok or deploy to a cloud instance for testing.
Step 2: Register the Endpoint in Stripe
- Go to Stripe Dashboard → Developers → Webhooks
- Click Add endpoint
- Paste your n8n webhook URL
- Select the events you want to handle (start with the five below)
- Save — Stripe will show you the Signing Secret (starts with
whsec_)
Events to select:
payment_intent.succeededpayment_intent.payment_failedinvoice.payment_succeededinvoice.payment_failedcustomer.subscription.deleted
Step 3: Verify the Webhook Signature
Stripe signs every webhook with an HMAC-SHA256 signature. Verifying it ensures your workflow only processes real Stripe events — not spoofed requests.
After the Webhook trigger node, add a Code node:
const stripe = require('crypto');
const payload = $input.first().json.body;
const sigHeader = $input.first().headers['stripe-signature'];
const secret = 'whsec_YOUR_SIGNING_SECRET';
const parts = sigHeader.split(',').reduce((acc, part) => {
const [key, val] = part.split('=');
acc[key] = val;
return acc;
}, {});
const timestamp = parts['t'];
const sig = parts['v1'];
const signedPayload = `${timestamp}.${payload}`;
const expectedSig = stripe.createHmac('sha256', secret).update(signedPayload, 'utf8').digest('hex');
if (expectedSig !== sig) { throw new Error('Invalid Stripe signature'); }
const event = JSON.parse(payload);
return [{ json: event }];
Gotcha: n8n's Webhook node parses the body by default. You need the raw body for signature verification. Set Raw Body to
truein the Webhook node options.
Step 4: Route Events with a Switch Node
Add a Switch node to route different event types:
-
Output 1:
{{ $json.type }}equalspayment_intent.succeeded -
Output 2:
{{ $json.type }}equalspayment_intent.payment_failed -
Output 3:
{{ $json.type }}equalsinvoice.payment_succeeded -
Output 4:
{{ $json.type }}equalsinvoice.payment_failed -
Output 5:
{{ $json.type }}equalscustomer.subscription.deleted - Fallback: log and ignore everything else
The Five Events That Matter
1. payment_intent.succeeded — One-Time Payment Confirmed
Fires when a customer completes a one-time charge. Use this to:
- Trigger product delivery (email a download link, provision access)
- Write the sale to Google Sheets or Airtable
- Send a Slack notification to your team
Key fields:
$json.data.object.amount // amount in cents
$json.data.object.currency // e.g., \"usd\"
$json.data.object.receipt_email // customer email
$json.data.object.metadata // your custom fields
2. payment_intent.payment_failed — Charge Failed
Fires when a card is declined or payment fails. Use this to:
- Send a \"payment failed\" email with a retry link
- Pause service access after N failures
- Alert your team for high-value orders
Key fields:
$json.data.object.last_payment_error.message
$json.data.object.last_payment_error.code
3. invoice.payment_succeeded — Subscription Renewal Paid
Fires every subscription invoice payment. Use this to:
- Extend customer access
- Send renewal receipts
- Update MRR tracking
Key fields:
$json.data.object.customer_email
$json.data.object.amount_paid
$json.data.object.subscription
4. invoice.payment_failed — Subscription Payment Failed
Critical for retention. Most involuntary churn is recoverable if you act fast.
Use this to:
- Send \"please update your card\" email immediately
- Trigger dunning (Day 1, Day 4, Day 7)
- Flag customer for manual follow-up
5. customer.subscription.deleted — Subscription Cancelled
Fires when a subscription ends. Use this to:
- Revoke access
- Send cancellation confirmation
- Trigger win-back sequence after 7 days
Three Complete Workflow Patterns
Pattern 1: Automated Payment Receipt + Delivery
Webhook → Verify Signature → Switch (event type)
→ payment_intent.succeeded
→ Extract: email, amount, metadata
→ Gmail: send receipt + download link
→ Google Sheets: log sale
Result: Every successful payment triggers a receipt email and auto-logs within seconds.
Pattern 2: Failed Payment Recovery
Webhook → Verify Signature → Switch
→ invoice.payment_failed
→ IF attempt_count == 1: \"Card failed\" email
→ IF attempt_count == 2: \"Last chance\" email
→ IF attempt_count >= 3: mark churned + pause access
Result: Automated dunning recovers 20-40% of failed subscription payments without manual work.
Pattern 3: Subscription Lifecycle Tracker
Webhook → Verify Signature → Switch
├→ invoice.payment_succeeded → Sheets: upsert (status=active)
├→ invoice.payment_failed → Sheets: update (status=past_due)
└→ subscription.deleted → Sheets: update (status=cancelled)
→ Wait 7 days → Send win-back email
Result: Real-time subscription dashboard in Google Sheets with zero manual updates.
Free Workflow JSON
I've built the complete workflow: Webhook trigger → Signature verification → Switch routing → all 5 event handlers with Gmail/Sheets stubs.
Download free n8n Stripe Webhook Workflow JSON →
Import in n8n: Settings → Import from file. Add your credentials and webhook signing secret, activate, and you're live.
6 Gotchas to Know
Raw body requirement: Stripe signature verification needs the exact raw request body. Check your n8n Webhook node for \"Raw Body\" option.
30-second response window: Stripe fails webhooks if no 200 within 30s. Use
Response Mode: Immediatelyand do heavy work after.Idempotency: Stripe retries failed webhooks for 3 days. Check
event.idin Sheets to skip duplicates.livemodeflag: Stripe sends both test and live events to the same endpoint. Check$json.livemode === trueto filter.payment_intent.succeeded+ subscriptions: First subscription charge fires bothpayment_intent.succeededandinvoice.payment_succeeded. Handle only one.Metadata is your friend: Add
metadatafields to Stripe Payment Intents. They come through in webhooks and let you route without extra API calls.
Want This Built For You?
If you'd rather have a working Stripe webhook workflow built to your spec — event types, routing, email templates, Sheets schema — I build and test it within 48 hours.
$99 Done-For-You n8n Workflow →
Fill out a short intake form describing what you need (which events, what actions, integrations). I build and deliver the workflow JSON ready to import.
Publishing free n8n workflow guides and automation templates. Subscribe for the full library:
Top comments (0)