← Back to Webhook Academy

Stripe Webhook Setup Guide

How to receive, verify, test, and monitor Stripe webhooks — step by step with code examples.

Last updated March 2026. Covers Stripe API version 2025-12 and later.

1

What Are Stripe Webhooks?

Stripe uses webhooks to notify your application when events happen in your account — a payment succeeds, a subscription renews, a dispute is opened. Instead of polling the Stripe API, your app receives an HTTP POST with a JSON payload the moment something occurs.

Common Stripe webhook event types:

payment_intent.succeeded payment_intent.payment_failed checkout.session.completed customer.subscription.created customer.subscription.updated customer.subscription.deleted invoice.payment_succeeded invoice.payment_failed charge.refunded charge.dispute.created
2

Setting Up Your Endpoint

Create an HTTP POST route that accepts Stripe's JSON payloads. Here's a minimal Node.js Express example:

const express = require('express');
const app = express();

// Important: use raw body for signature verification
app.post('/webhook/stripe',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const event = JSON.parse(req.body);

    switch (event.type) {
      case 'payment_intent.succeeded':
        const paymentIntent = event.data.object;
        console.log('Payment succeeded:', paymentIntent.id);
        // Fulfill the order, update DB, send receipt...
        break;
      case 'invoice.payment_failed':
        console.log('Invoice payment failed:', event.data.object.id);
        // Notify customer, retry logic...
        break;
      default:
        console.log('Unhandled event type:', event.type);
    }

    res.status(200).json({ received: true });
  }
);

app.listen(3000, () => console.log('Webhook server on port 3000'));

Key points: always return a 200 response quickly. Process heavy work asynchronously (queue, background job). Stripe retries on non-2xx responses for up to 3 days.

3

Verifying Stripe Signatures

Never trust a webhook payload without verifying its signature. Stripe signs every webhook with your endpoint secret (starts with whsec_). Here's how:

const stripe = require('stripe')('sk_...');
const endpointSecret = 'whsec_...';

app.post('/webhook/stripe',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,    // raw body, NOT parsed JSON
        sig,
        endpointSecret
      );
    } catch (err) {
      console.error('Signature verification failed:', err.message);
      return res.status(400).send('Webhook signature verification failed');
    }

    // Event is verified — safe to process
    console.log('Verified event:', event.type);
    res.status(200).json({ received: true });
  }
);

Critical: pass the raw request body to constructEvent, not parsed JSON. If you use express.json() middleware globally, it will break verification. Use express.raw({ type: 'application/json' }) on the webhook route specifically.

4

Testing with SDNBros

During development you need a public URL that Stripe can reach. Here's the fastest path:

  1. Open the SDNBros Webhook Tester — you get a public endpoint URL instantly, no signup required.
  2. In the Stripe Dashboard, go to Developers > Webhooks > Add endpoint. Paste your SDNBros URL.
  3. Select the events you want (e.g. payment_intent.succeeded, checkout.session.completed).
  4. Trigger a test event using Stripe's "Send test webhook" button, or run stripe trigger payment_intent.succeeded from the Stripe CLI.
  5. Switch back to SDNBros — you'll see the event arrive in real time with full headers, body, and timing. SDNBros auto-detects it as a Stripe webhook and shows the event type.

For local development, use sdnbros tunnel to forward webhooks from your SDNBros endpoint directly to localhost:3000.

5

Monitoring in Production

In production, webhook failures are silent killers — a missed invoice.payment_failed event means a churned customer you never knew about. Set up proper monitoring:

Create a free SDNBros account to start monitoring your Stripe webhooks in production.

Common Stripe Webhook Errors

ErrorCauseFix
Signature verification failedUsing parsed JSON body instead of raw body, or wrong endpoint secretUse express.raw() on the webhook route. Check your whsec_ secret matches the endpoint in Stripe Dashboard.
Webhook timeout (no response)Endpoint takes longer than 20 seconds to respondReturn 200 immediately and process the event asynchronously in a background worker.
Duplicate events receivedStripe retries on non-2xx or timeout. Your handler processes the same event twice.Store the event ID (evt_...) and check for duplicates before processing. Make your handler idempotent.
400 Bad Request from StripeEndpoint URL is unreachable or returns HTML instead of JSONVerify the URL is correct and publicly accessible. Check for WAF or proxy rules blocking Stripe's IPs.
Events not arrivingWrong events selected in Stripe Dashboard, or endpoint disabled after too many failuresCheck Stripe Dashboard > Webhooks > your endpoint. Re-enable if disabled. Verify selected event types.

Test Stripe webhooks free

Create a public endpoint and inspect Stripe events. No signup is needed for the tester.

Open Webhook Tester Monitor in Production

Related guides: What is a Webhook? · Webhook vs API · Webhook Security · Retry Strategies · GitHub · Shopify · Slack

More: What is a Webhook? | Webhook Security Guide | Stripe Integration Guide | Compare Tools