Encrypt Online
Choose theme

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 readPasswords & Hashing
Capturing Raw Request Bodies for Webhook Verification in Node, Express, and Next.js guide cover

Tip

Keep the exact input bytes stable while you test. One changed newline, encoding step, or parser pass can change a hash or signature.

Webhook signature verification usually fails for one dull reason: the server is no longer holding the exact bytes 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.

Express: capture the bytes before JSON middleware changes them

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.

Next.js Route Handlers: read the request body once, as text or bytes

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 note that Route Handlers do not need extra bodyParser configuration. The important part is still the same: verify before you parse the body into JSON for normal application logic.

What to log and what not to log

During a mismatch investigation, log structure sparingly:

  • keep header names, request id, and timestamp context
  • avoid dumping the shared secret
  • avoid pasting the full failing payload into tickets until it is redacted

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 do not match

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?

No. 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. Do not paste the shared secret or raw unredacted deliveries into tickets.

Developer workflow

Use this guide as an implementation check before you depend on a digest, password hash, or signature in production logic.

  1. Freeze the exact input bytes, including encoding and newline handling.
  2. Generate or verify the digest with a small known sample.
  3. Record the algorithm, comparison rule, and storage format where future maintainers can find it.
Text
1. exact input bytes
2. hash or HMAC operation
3. constant-format comparison
4. document algorithm and encoding

References