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:
// Stripe signature verification
const stripe = require('crypto');
const payload = $input.first().json.body; // raw body as string
const sigHeader = $input.first().headers['stripe-signature'];
const secret = 'whsec_YOUR_SIGNING_SECRET'; // store in n8n credential
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 — request rejected');
}
// Parse the event
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 Binary Data to
falseand Raw Body totruein the Webhook node's options, then access$input.first().json.rawBodyor the header equivalent depending on your n8n version.
In practice: for non-critical internal workflows, you can skip signature verification. For production customer-facing workflows, always verify.
Step 4: Route Events with a Switch Node
Add a Switch node after verification 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 output: everything else (log and ignore)
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 a payment attempt fails. Use this to:
- Send a "payment failed" email to the customer with a retry link
- Pause their service access after N failed attempts
- Alert your team for high-value orders
Key fields:
$json.data.object.last_payment_error.message // e.g., "Your card was declined"
$json.data.object.last_payment_error.code // e.g., "card_declined"
3. invoice.payment_succeeded — Subscription Renewal Paid
Fires every time a subscription invoice is paid — both the first charge and renewals. Use this to:
- Extend the customer's access period in your database
- Send a renewal receipt
- Update your MRR tracking spreadsheet
Key fields:
$json.data.object.customer_email
$json.data.object.amount_paid
$json.data.object.subscription // subscription ID
$json.data.object.period_end // Unix timestamp of next renewal
4. invoice.payment_failed — Subscription Payment Failed
Fires when a recurring charge fails (expired card, insufficient funds). This is critical for retention — most involuntary churn is recoverable if you act fast.
Use this to:
- Send a "please update your card" email immediately
- Trigger a dunning sequence (Day 1 email, Day 4 SMS, Day 7 final notice)
- Flag the customer in your CRM for manual follow-up
Key fields:
$json.data.object.customer_email
$json.data.object.next_payment_attempt // Unix timestamp Stripe will retry
$json.data.object.attempt_count // how many times Stripe has tried
5. customer.subscription.deleted — Subscription Cancelled
Fires when a subscription ends (cancellation at period end resolves here, not at cancel time). Use this to:
- Revoke access
- Send a cancellation confirmation + offboarding email
- Trigger a win-back sequence after 7 days
- Write to a churn tracking sheet
Key fields:
$json.data.object.customer // customer ID
$json.data.object.cancel_at_period_end
$json.data.object.ended_at
Three Complete Workflow Patterns
Pattern 1: Automated Payment Receipt + Delivery
Webhook → Verify Signature → Switch (event type)
└─ payment_intent.succeeded
→ Extract: email, amount, metadata.product_id
→ Gmail/Resend: send receipt + download link
→ Google Sheets: append row (date, email, amount, product)
Result: Every successful payment triggers a branded receipt and fulfillment email within seconds, with every sale recorded automatically.
Pattern 2: Failed Payment Recovery Sequence
Webhook → Verify Signature → Switch
└─ invoice.payment_failed
→ IF attempt_count == 1: send "Heads up, card failed" email
→ IF attempt_count == 2: send "Last chance" email with update link
→ IF attempt_count >= 3: send to CRM as churned + pause access
Result: Automated dunning that recovers 20–40% of failed subscription payments without manual intervention.
Pattern 3: Subscription Lifecycle Tracker
Webhook → Verify Signature → Switch
├─ invoice.payment_succeeded → Sheets: upsert row (customer, status=active, last_paid=now)
├─ invoice.payment_failed → Sheets: update row (status=past_due, attempts=N)
└─ subscription.deleted → Sheets: update row (status=cancelled, ended_at=now)
→ Wait 7 days → Send win-back email
Result: A real-time subscription dashboard in Google Sheets with zero manual updates, plus automated win-back outreach.
Free Workflow JSON
Download the complete workflow JSON (Webhook trigger → Signature verification → Switch node routing → all 5 event handlers with Gmail/Sheets stubs):
Download free n8n Stripe Webhook workflow JSON →
Import it in n8n: Settings → Import from file. Add your Stripe credentials and webhook signing secret, activate, and you're live.
6 Gotchas to Know
Raw body requirement: Stripe signature verification requires the exact raw request body. If n8n parses the JSON before you can read it, the HMAC won't match. Check your n8n version's Webhook node options for "Raw Body."
30-second response window: Stripe marks a webhook as failed if it doesn't receive a 200 within 30 seconds. Use
Response Mode: Immediatelyon the Webhook node and do the heavy work (Sheets writes, emails) after responding.Idempotency: Stripe retries failed webhooks for up to 3 days. Your workflow will receive duplicate events. Add a check node that looks up the
event.idin a Sheets or database table and skips processing if already handled.livemodeflag: Stripe sends both test and live events to the same endpoint if you use the same URL. Check$json.livemode === trueat the top of your Switch node to avoid processing test events in production.payment_intent.succeededfires for subscriptions too: The first subscription charge fires bothpayment_intent.succeededandinvoice.payment_succeeded. Handle only one to avoid double-processing.Metadata is your friend: Add
metadatafields to your Stripe Payment Intents (product ID, user ID, plan name). They come through in webhook payloads and let you route fulfillment without additional API calls.
Want This Built For You?
If you'd rather have a working Stripe webhook workflow built to your exact spec — event types, routing logic, email templates, Sheets schema — I'll build it for you.
$99 Done-For-You n8n Workflow →
You fill out a short intake form describing what you need (which events, what actions, what integrations). I build and deliver the workflow JSON within 48 hours, tested and ready to import.
Also publishing free n8n workflow guides and automation templates at pirateprentice.gumroad.com — check there for the full library.
Top comments (0)