Debug your next webhook with Probe.
All articles
#Stripe#testing#webhooks#payments·6 min read

How to Test Stripe Webhooks Locally: The Complete Step-by-Step Guide

A practical, battle-tested guide for capturing, verifying, and debugging Stripe webhooks locally without signature errors or payload corruption.

Probe

Probe Team

Published September 16, 2026

Testing Stripe webhooks in a local development environment is a necessary step before handling real credit card transactions in production. According to Stripe's developer documentation, webhook signatures are generated using an HMAC-SHA256 hash over the combined timestamp and the exact raw byte payload, with a strict 300-second (5-minute) tolerance window to prevent replay attacks.

If your local server alters a single byte of whitespace or reformats JSON before verification, Stripe's SDK will throw a SignatureVerificationError. This guide walks through an end-to-end workflow to reliably capture, verify, and replay Stripe webhook events locally.

The Stripe Signature Verification Pipeline

Understanding how Stripe computes signatures makes debugging local validation failures straightforward. Stripe transmits a Stripe-Signature header containing a UNIX timestamp (t=...) and one or more signature hashes (v1=...).

Stripe Webhook Signature Verification PipelineA flow diagram illustrating how timestamp and raw body are concatenated with a period, hashed using HMAC-SHA256 with the webhook signing secret, and verified with constant-time equality against the Stripe-Signature header.Header Timestampt=1788102400Raw Request BodyExact UTF-8 BytesPayload Concatenationt + "." + rawBodyHMAC-SHA256Secret: whsec_...Constant-Time Comparison

timingSafeEqual(computedHash, v1_signature)

Stripe Tolerance Check: (currentTime - t) < 300 seconds (5 minutes)

Cryptographic verification pipeline required by Stripe to authenticate incoming events.

Handling Raw Bodies in Modern Frameworks

The number one cause of local Stripe webhook verification errors is body parser middleware. Frameworks often convert JSON automatically, which alters formatting.

1. TanStack Start / Modern Fetch Handlers

In modern web standards, fetch requests provide direct access to the stream before JSON conversion:

ts
import { Stripe } from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',
});

export async function POST(request: Request) {
  const signature = request.headers.get('stripe-signature');
  if (!signature) {
    return new Response('Missing stripe-signature header', { status: 400 });
  }

  // Preserve the raw string directly from the HTTP stream
  const rawBody = await request.text();

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      rawBody,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err: unknown) {
    const message = err instanceof Error ? err.message : 'Unknown error';
    console.error(`Webhook verification failed: ${message}`);
    return new Response(`Webhook Error: ${message}`, { status: 400 });
  }

  // Handle verified events
  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object as Stripe.Checkout.Session;
      await fulfillOrder(session.id);
      break;
    }
    case 'customer.subscription.updated': {
      const subscription = event.data.object as Stripe.Subscription;
      await syncSubscriptionStatus(subscription);
      break;
    }
  }

  return new Response(JSON.stringify({ received: true }), { status: 200 });
}

2. Express.js Raw Body Setup

In Express, ensure express.raw() handles the webhook route before global express.json() is registered:

ts
import express from 'express';
import Stripe from 'stripe';

const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// CRITICAL: Mount raw parser specifically for the Stripe route
app.post(
  '/api/webhooks/stripe',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event: Stripe.Event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body, // Buffer from express.raw
        sig as string,
        process.env.STRIPE_WEBHOOK_SECRET!
      );
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : 'Invalid signature';
      return res.status(400).send(`Webhook Error: ${message}`);
    }

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

// Global JSON parser mounted after webhook route
app.use(express.json());

Capturing and Inspecting Stripe Events with Probe

While developing complex checkout flows, triggering live webhooks from the Stripe Dashboard to a local server can be tedious. Using Probe, you can create a dedicated endpoint in seconds:

  1. Create a new test endpoint in your Probe workspace.
  2. In the Stripe Dashboard > Developers > Webhooks, add the Probe endpoint URL (e.g. https://probe.monobase.io.vn/hooks/ep_dev_stripe).
  3. Select events you wish to test, such as checkout.session.completed and payment_intent.payment_failed.
  4. Trigger a test event using Stripe's "Send test webhook" button.
  5. In Probe, inspect the exact headers, query parameters, timestamp, and JSON body payload.
  6. Copy the captured payload into your test fixtures to run repeatable automated integration tests.

4 Critical Scenarios to Test Before Production

A robust Stripe webhook handler must handle edge cases gracefully:

  1. Successful Checkout (checkout.session.completed): Verify that customer orders are provisioned and order records are saved in your database.
  2. Payment Failure (invoice.payment_failed): Ensure user accounts are notified and subscription grace periods are properly marked.
  3. Duplicate Events: Stripe guarantees at-least-once delivery. Re-sending the exact same event ID must not double-fulfill an order.
  4. Out-of-Order Events: If customer.subscription.deleted arrives before customer.subscription.updated due to network routing, your state machine should retain the final deleted state.

Frequently Asked Questions

Probe Live Inspection
Ready to inspect incoming webhooks in real time?
Create a dedicated Probe endpoint to capture headers, verify raw byte payloads, and debug delivery failures before deploying to production.
Start debugging nowFree tier · Instant setup · TLS 1.3
Probe

Probe Team

Probe is a focused webhook debugging platform designed for engineering teams. We build telemetry and developer inspection tooling to eliminate integration guesswork.

All guides are regularly verified against Stripe, GitHub, Shopify, and RFC specifications.

More Webhook Guides
All articles
September 16, 20267 min read
A step-by-step developer checklist for diagnosing missing, timed-out, rejected, or duplicate webhook deliveries with reproducible test workflows.
#debugging#webhooks
Read guide
September 16, 20266 min read
Learn how to receive, inspect, and replay webhook events during local development using modern inspection endpoints and automated test fixtures.
#webhooks#local development
Read guide

Developer Webhook Telemetry

Start debugging your webhooks with Probe

Never guess what an API sent again. Real-time payload inspection, raw headers, and replay tools.