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

EventWhen TriggeredTypical Action
checkout.session.completedCheckout completedGrant access
payment_intent.succeededPayment successfulSend invoice
payment_intent.failedPayment failedNotify customer
invoice.paidInvoice paidExtend subscription
invoice.payment_failedSubscription payment failedSend reminder
customer.subscription.deletedSubscription cancelledRevoke access
customer.subscription.updatedSubscription changedAdjust plan
charge.refundedRefund issuedRevoke access
charge.dispute.createdChargebackAlert team

Step 1: Create Webhook Endpoint

Option A: n8n Webhook

  1. Create new workflow
  2. Add node: Webhook
  3. Configuration:
    • HTTP Method: POST
    • Path: stripe-webhook
  4. Copy URL: https://your-n8n-instance.com/webhook/stripe-webhook

Option B: Make.com Webhook

  1. Create new scenario
  2. Trigger: Webhooks > Custom webhook
  3. Copy URL and enter in Stripe

Step 2: Register Webhook with Stripe

In Stripe Dashboard

  1. Dashboard -> Developers -> Webhooks
  2. Click "Add endpoint"
  3. Enter endpoint URL
  4. Select events (e.g., checkout.session.completed)
  5. Confirm "Add endpoint"

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

  1. Webhooks -> Custom Webhook
  2. JSON -> Parse JSON
  3. Router -> By event type
  4. HTTP -> Call your API
  5. Email -> Send confirmation
  6. Slack -> Notify team

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:

AttemptWait Time
1Immediately
21 hour
36 hours
424 hours
548 hours

Tip: Always respond with status 200, even on errors - and process asynchronously.

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:

  1. Developers -> Webhooks -> Select endpoint
  2. Click "Send test webhook"
  3. Select event type
  4. "Send test webhook"

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

  1. Open workflow
  2. Activate "Test Workflow"
  3. Send Stripe test event
  4. Inspect data

Debugging

Event Logs in Stripe

Dashboard -> Developers -> Webhooks -> Events

Shows:

  • All sent events
  • HTTP response from your server
  • Retry attempts

Common Errors

ErrorCauseSolution
401 UnauthorizedWrong API keyCheck key
400 Bad RequestInvalid dataCheck payload
500 Internal ErrorYour code has bugsCheck logs
TimeoutProcessing too slowProcess async
Signature mismatchWrong signing secretCheck secret

Production Checklist

○ Signature verification implemented
○ Idempotency check built in
○ All relevant events subscribed
○ Error handling implemented
○ Monitoring/alerting set up
○ Test mode tested
○ Live mode tested
○ Backup processing for failures

Costs

ComponentCost
Stripe WebhooksFree
n8n (1000 executions)~20 EUR/month
Make.com (10,000 ops)~30 EUR/month

ROI: Automatic processing saves 5-10h/week with 100+ transactions.

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

  1. Create webhook endpoint (n8n/Make.com)
  2. Register with Stripe
  3. Implement signature verification
  4. Process first event (checkout.session.completed)
  5. Add more events

Once the webhook is running reliably, the natural next step is posting those events into accounting -- that is exactly what the Stripe to DATEV integration does. We support you with Stripe integration - from setup to complex subscription logic.