Encrypt Online
Theme

Passwords & Hashing

Capturing Raw Request Bodies for Webhook Verification in Node, Express, and Next.js

Preserve the exact request bytes before parsing JSON so Stripe, GitHub, and other webhook signatures stop failing for boring reasons.

Encrypt Online Editorial Team3 min read
Encrypt Online guide cover on a lilac background with the headline "Capture the raw body". One request endpoint joins a right-angle incoming arrow at the circle edge. The connector never crosses the circle interior; one clean tangent join replaces the overlapping start. The downward arrow uses a lighter 1.75-unit stroke. Direction-path weight for this drawing: 1.75 units.

Webhook signature verification usually fails because the server no longer holds the exact bytes that the provider signed.

Once JSON parsing, pretty-printing, newline conversion, or middleware rewriting gets involved, the payload may look identical while the signature no longer matches.

Why parsed JSON breaks valid signatures

Providers sign the raw request body, not your framework's reconstructed object.

Text
signed bytes: {"event":"invoice.paid","id":"evt_123"}
parsed body:  { event: "invoice.paid", id: "evt_123" }
re-serialized: {"id":"evt_123","event":"invoice.paid"}

The parsed object can contain the same information while the byte sequence is already different. Stripe's docs call this out directly: any manipulation of the raw body causes signature verification to fail.

Capture Raw Bytes Before Express JSON Middleware

In Express, the decisive step is to use raw-body handling on the webhook route before generic JSON parsing reshapes the payload.

JavaScript
import express from "express";

const app = express();

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const rawBody = req.body.toString("utf8");
  const signature = req.get("Stripe-Signature");

  // Verify rawBody + signature + endpoint secret here.
  res.sendStatus(200);
});

If a global express.json() middleware runs first on that route, you have usually already lost the original bytes.

Read a Next.js Route Handler Request Body Once

In Next.js Route Handlers, the Web Request API gives you the raw body directly.

TypeScript
export async function POST(request: Request) {
  const rawBody = await request.text();
  const signature = request.headers.get("stripe-signature");

  // Verify rawBody + signature + endpoint secret here.
  return new Response("ok");
}

The Next.js docs say Route Handlers need no extra bodyParser configuration. Verify the raw body before parsing it as JSON for application logic.

Choose safe debugging details

During a mismatch investigation, log structure sparingly:

  • keep header names, request id, and timestamp context
  • keep the shared secret out of logs
  • redact the failing payload before adding it to a ticket

Use Secret Redactor before sharing payload samples or provider headers with another person.

Use the verifier before you change anything else

Webhook Signature Verify is the fastest way to check whether the problem is:

  • wrong secret
  • wrong provider header
  • wrong algorithm or signing string
  • bytes changed before verification

If you still need to reproduce the digest manually, move to HMAC Generator after you have confirmed the exact raw body string.

Questions when the bytes still differ

Why does verification fail even when the JSON looks identical?

Because the provider signed the original bytes, not the parsed object you see after middleware touched it.

Should I parse the body first and verify later?

Preserve the raw body first, verify it, and only then parse it for normal application handling.

What is the safest thing to share while debugging?

Share redacted payload samples, header names, and mismatch symptoms. Keep the shared secret and raw unredacted deliveries inside the protected debugging environment.

References