Set Up Stripe Webhooks: Payment Automation with n8n and Make.com
Copy-paste webhook config for Stripe + n8n/Make.com. Covers signature verification, the 8 critical events, and the #1 mistake that breaks payments.
Stripe webhooks are the key to fully automated payment processes. Instead of manually checking whether a payment came through, your system reacts automatically to every event: payment successful, subscription cancelled, chargeback - all in real-time. In this guide, we show you the complete setup.
What Are Stripe Webhooks?
Webhooks are HTTP callbacks: Stripe sends a message to your URL as soon as something happens.
Without webhooks:Customer pays -> You check manually -> Hours later: Access granted
With webhooks:
Customer pays -> Stripe sends event -> Immediately: Access granted
Important Stripe Events
| Event | When Triggered | Typical Action |
|---|---|---|
checkout.session.completed | Checkout completed | Grant access |
payment_intent.succeeded | Payment successful | Send invoice |
payment_intent.failed | Payment failed | Notify customer |
invoice.paid | Invoice paid | Extend subscription |
invoice.payment_failed | Subscription payment failed | Send reminder |
customer.subscription.deleted | Subscription cancelled | Revoke access |
customer.subscription.updated | Subscription changed | Adjust plan |
charge.refunded | Refund issued | Revoke access |
charge.dispute.created | Chargeback | Alert team |
Step 1: Create Webhook Endpoint
Option A: n8n Webhook
- HTTP Method: POST
- Path: stripe-webhook
https://your-n8n-instance.com/webhook/stripe-webhookOption B: Make.com Webhook
Step 2: Register Webhook with Stripe
In Stripe Dashboard
checkout.session.completed)Copy Webhook Secret
After creation, Stripe shows a Signing Secret:
whsec_1234567890abcdefghijklmnop
Important: Store this key securely - for signature verification!
Via Stripe CLI (for Development)
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to localhost
stripe listen --forward-to localhost:5678/webhook/stripe-webhook
# Shows temporary signing secret
Step 3: Verify Signature (Security!)
Why important? Without verification, anyone could send fake events to your endpoint.n8n: Check Signature
// Node: Code (before all other nodes)
const crypto = require('crypto');
const payload = $json.body; // Raw body
const signature = $headers['stripe-signature'];
const webhookSecret = 'whsec_1234567890abcdefghijklmnop';
// Stripe signature format: t=timestamp,v1=signature
const elements = signature.split(',');
const timestamp = elements.find(e => e.startsWith('t=')).split('=')[1];
const expectedSig = elements.find(e => e.startsWith('v1=')).split('=')[1];
// Calculate signature
const signedPayload = ${timestamp}.${JSON.stringify(payload)};
const computedSig = crypto
.createHmac('sha256', webhookSecret)
.update(signedPayload)
.digest('hex');
if (computedSig !== expectedSig) {
throw new Error('Invalid signature');
}
// Check timestamp (max 5 minutes old)
const now = Math.floor(Date.now() / 1000);
if (now - parseInt(timestamp) > 300) {
throw new Error('Timestamp too old');
}
return $json;
Make.com: HTTP Module with Verification
In Make.com, you can add an HTTP module that verifies the signature.
Step 4: Process Events
Workflow: Checkout Completed
Stripe Webhook
(checkout.session.completed)
|
Verify signature
|
Extract customer data
|
Parallel:
+- Database: Create/activate user
+- Email: Send welcome email
+- CRM: Mark deal as won
+- Slack: Notify team
n8n Implementation
Node 1: Webhook Trigger// Webhook receives event
{
"type": "checkout.session.completed",
"data": {
"object": {
"id": "cs_1234",
"customer": "cus_1234",
"customer_email": "customer@example.com",
"amount_total": 9900,
"currency": "eur",
"metadata": {
"plan": "pro",
"user_id": "123"
}
}
}
}
Node 2: Switch by Event Type
// Node: Switch
const eventType = $json.type;
switch(eventType) {
case 'checkout.session.completed':
return { route: 'checkout_completed' };
case 'invoice.paid':
return { route: 'invoice_paid' };
case 'customer.subscription.deleted':
return { route: 'subscription_cancelled' };
default:
return { route: 'unknown' };
}
Node 3: Extract Data
// Node: Set
{
"customerId": "{{ $json.data.object.customer }}",
"email": "{{ $json.data.object.customer_email }}",
"amount": "{{ $json.data.object.amount_total / 100 }}",
"plan": "{{ $json.data.object.metadata.plan }}",
"userId": "{{ $json.data.object.metadata.user_id }}"
}
Node 4: Activate User (Your Database)
// Node: HTTP Request (to your API)
{
"method": "POST",
"url": "https://api.your-app.com/users/activate",
"body": {
"userId": "{{ $json.userId }}",
"plan": "{{ $json.plan }}",
"stripeCustomerId": "{{ $json.customerId }}"
}
}
Workflow: Subscription Payment Failed
Stripe Webhook
(invoice.payment_failed)
|
Check attempt number
|
Switch:
+- Attempt 1: Friendly email
+- Attempt 2: Urgent email
+- Attempt 3: Final warning
+- Attempt 4: Suspend access
Implementation
// Node: Code
const invoice = $json.data.object;
const attemptCount = invoice.attempt_count;
let action, emailTemplate;
switch(attemptCount) {
case 1:
action = 'soft_reminder';
emailTemplate = 'payment_failed_soft';
break;
case 2:
action = 'urgent_reminder';
emailTemplate = 'payment_failed_urgent';
break;
case 3:
action = 'final_warning';
emailTemplate = 'payment_failed_final';
break;
default:
action = 'suspend_account';
emailTemplate = 'account_suspended';
}
return { action, emailTemplate, attemptCount };
Workflow: Refund
Stripe Webhook
(charge.refunded)
|
Full or partial?
|
Full:
+- Revoke access
+- Create internal note
+- Notify team
|
Partial:
+- Just log
Workflow: Chargeback (Dispute)
Stripe Webhook
(charge.dispute.created)
|
IMMEDIATE alert to team!
|
Slack: @channel Alert
|
Zendesk: High-priority ticket
|
Gather all customer data
(for Stripe dispute response)
Important: Chargebacks cost $15 in fees and can lead to account suspension!
Make.com Scenario: Stripe Checkout
Module Setup
Filter Example
// Only for certain products
Condition: {{data.object.metadata.product_type}} = "subscription"
Advanced Patterns
Ensure Idempotency
Stripe can send events multiple times. Prevent duplicate processing:
// Node: Code
const eventId = $json.id; // e.g., "evt_1234"
// Check if already processed
const existing = await getFromDatabase('stripe_events', eventId);
if (existing) {
return { skip: true, reason: 'already_processed' };
}
// Mark as processed
await saveToDatabase('stripe_events', {
id: eventId,
type: $json.type,
processedAt: new Date().toISOString()
});
return { skip: false };
Retry Logic
If your endpoint fails, Stripe retries:
| Attempt | Wait Time |
|---|---|
| 1 | Immediately |
| 2 | 1 hour |
| 3 | 6 hours |
| 4 | 24 hours |
| 5 | 48 hours |
Webhook Queue for Reliability
Stripe Webhook
|
Save to queue (Redis/SQS)
|
Immediately respond 200 OK
|
[Asynchronously]
Worker processes queue
Testing
Stripe Test Events
In Stripe Dashboard:
Stripe CLI
# Send test event
stripe trigger checkout.session.completed
# With custom data
stripe trigger payment_intent.succeeded \
--add payment_intent:metadata.user_id=123
n8n Test Mode
Debugging
Event Logs in Stripe
Dashboard -> Developers -> Webhooks -> Events
Shows:
- All sent events
- HTTP response from your server
- Retry attempts
Common Errors
| Error | Cause | Solution |
|---|---|---|
| 401 Unauthorized | Wrong API key | Check key |
| 400 Bad Request | Invalid data | Check payload |
| 500 Internal Error | Your code has bugs | Check logs |
| Timeout | Processing too slow | Process async |
| Signature mismatch | Wrong signing secret | Check secret |
Production Checklist
Costs
| Component | Cost |
|---|---|
| Stripe Webhooks | Free |
| n8n (1000 executions) | ~20 EUR/month |
| Make.com (10,000 ops) | ~30 EUR/month |
Conclusion
Stripe webhooks are essential for any SaaS/e-commerce business:
- Real-time response to payments
- Fully automatic subscription management
- Proactive churn management
- Compliance (invoices, etc.)
Next Steps
We support you with Stripe integration - from setup to complex subscription logic.
Get Automation Insights That Matter
New tool comparisons, workflow tips, and pricing updates — directly in your inbox. No spam, unsubscribe anytime.
We respect your privacy. Unsubscribe at any time.