Stripe Experts
Shopify IntegrationPlatformsIndustriesHire UsBlog
Book a Free Consultation
Stripe Experts

Trusted Stripe integration services for SaaS, marketplaces, and e-commerce.

Company

  • About
  • Blog
  • Contact

Services

  • All Services
  • Stripe Checkout
  • Stripe Connect
  • Stripe Billing

Hire Us

  • All Hiring Options
  • Hire a Stripe Expert
  • Hire a Developer
  • Hire a Stripe Consultant
  • Migrate to Stripe

Industries

  • SaaS
  • Marketplace
  • Healthcare
  • Fintech

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Stripe Experts. All rights reserved.

Not affiliated with Stripe, Inc. Independent Stripe integration partner.

Powered by mzkzeeshan

bussiness@mzkzeeshan.com

  1. Home
  2. Blog
  3. Stripe Webhooks
  4. Stripe Webhooks in Express: The express.json() Trap
Stripe Webhooks

Stripe Webhooks in Express: The express.json() Trap

One line of body-parser middleware silently breaks every signature check. Here's the correct route ordering.

Z

Zeeshan

Founder · Published Aug 3, 2026

If you have ever seen "No signatures found matching the expected signature for payload" in an Express app, you have met this bug. It is not a wrong secret, and it is not a Stripe problem. It is express.json() parsing the body into an object before your webhook handler runs.

Why Parsing Breaks It

Stripe computes the signature over the exact bytes it transmitted. express.json() consumes the stream, parses it, and hands you req.body as a JavaScript object. Re-serializing that object gives you semantically identical JSON with different bytes: different key order, different whitespace, and therefore a different signature.

The Fix: Raw Body on That Route Only

import express from "express";
import Stripe from "stripe";

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

// The webhook route is mounted BEFORE the global JSON parser, with a raw
// parser scoped to just this path. Order matters. This cannot go after.
app.post(
  "/webhooks/stripe",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["stripe-signature"] as string;

    let event: Stripe.Event;
    try {
      event = stripe.webhooks.constructEvent(
        req.body, // a Buffer here, not an object
        signature,
        process.env.STRIPE_WEBHOOK_SECRET!,
      );
    } catch (err) {
      return res.status(400).send(`Webhook Error: ${(err as Error).message}`);
    }

    res.json({ received: true }); // Acknowledge first.
    void handleEvent(event); // Then work, off the response path.
  },
);

// Every other route gets normal JSON parsing.
app.use(express.json());

Why the Ordering Is the Whole Trick

Express middleware runs in registration order. If app.use(express.json()) appears above the webhook route, it has already consumed the stream by the time the raw parser is reached, and express.raw() will not un-parse it. Mounting the webhook first is not a style preference. It is the fix.

Make the Handler Idempotent

Stripe retries on non-2xx responses and on timeouts, and it can legitimately deliver the same event more than once. Record event.id in a table with a unique constraint and skip anything you have already processed.

  • Store event.id on first receipt, inside the same transaction as the side effect
  • Return 200 for a duplicate rather than erroring: a retry is not a failure
  • Acknowledge within a few seconds; queue anything slower than that
#express#webhooks#nodejs

Stripe insights, monthly

One email a month, no spam, unsubscribe anytime.

No spam, unsubscribe anytime.

On This Page

  • Why Parsing Breaks It
  • The Fix: Raw Body on That Route Only
  • Why the Ordering Is the Whole Trick
  • Make the Handler Idempotent

Related Services

Webhooks

Production-grade Stripe webhook handling, verified and monitored.

Learn more

API Integration

Custom Stripe API integration for your exact business logic.

Learn more

Stripe Checkout

Fully hosted, brand-matched Stripe Checkout, live in days.

Learn more

Related Reading

Stripe Webhooks

Handling Stripe Webhooks Reliably in Node.js

Verification, idempotency, and retries: the three things that separate a toy webhook handler from a production one.

Jan 26, 2026Read
Stripe Tutorials

Subscriptions in Django with Stripe Billing

Let Stripe own the billing state and Django own the entitlement. Mixing the two is where it goes wrong.

Aug 10, 2026Read
Stripe Tutorials

Stripe in PHP: Checkout Sessions and Webhook Verification Done Right

The raw-body problem bites PHP integrations harder than most. Here's the correct shape.

Jul 6, 2026Read