DEV Community

Pirate Prentice
Pirate Prentice

Posted on

n8n Stripe Webhooks: Handle Payment Events, Subscriptions, and Refunds Automatically (Free Workflow JSON)

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

  1. Open n8n and create a new workflow
  2. Add a Webhook node as the trigger
  3. Set HTTP Method to POST
  4. Copy the generated webhook URL — you'll add it to Stripe next
  5. 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

  1. Go to Stripe Dashboard → Developers → Webhooks
  2. Click Add endpoint
  3. Paste your n8n webhook URL
  4. Select the events you want to handle (start with the five below)
  5. Save — Stripe will show you the Signing Secret (starts with whsec_)

Events to select:

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • invoice.payment_succeeded
  • invoice.payment_failed
  • customer.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 }];
Enter fullscreen mode Exit fullscreen mode

Gotcha: n8n's Webhook node parses the body by default. You need the raw body for signature verification. Set Binary Data to false and Raw Body to true in the Webhook node's options, then access $input.first().json.rawBody or 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 }} equals payment_intent.succeeded
  • Output 2: {{ $json.type }} equals payment_intent.payment_failed
  • Output 3: {{ $json.type }} equals invoice.payment_succeeded
  • Output 4: {{ $json.type }} equals invoice.payment_failed
  • Output 5: {{ $json.type }} equals customer.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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

  1. 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."

  2. 30-second response window: Stripe marks a webhook as failed if it doesn't receive a 200 within 30 seconds. Use Response Mode: Immediately on the Webhook node and do the heavy work (Sheets writes, emails) after responding.

  3. 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.id in a Sheets or database table and skips processing if already handled.

  4. livemode flag: Stripe sends both test and live events to the same endpoint if you use the same URL. Check $json.livemode === true at the top of your Switch node to avoid processing test events in production.

  5. payment_intent.succeeded fires for subscriptions too: The first subscription charge fires both payment_intent.succeeded and invoice.payment_succeeded. Handle only one to avoid double-processing.

  6. Metadata is your friend: Add metadata fields 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)