deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Why JSON body parsers break Stripe and Shopify webhook signature checks in Node

A dev.to walkthrough explains why Node and TypeScript backends reject valid Stripe and Shopify webhooks: HMAC verification must run on the raw request bytes, not a re-serialized JSON body.

Why JSON body parsers break Stripe and Shopify webhook signature checks in Node

The pitfall

A walkthrough published on dev.to identifies what its author considers the most frequent cause of 401 Unauthorized responses when handling Stripe and Shopify webhooks in Node and TypeScript backends: verifying the HMAC-SHA256 signature against JSON.stringify(req.body) instead of the exact bytes the provider transmitted.

Both providers sign webhook deliveries by computing an HMAC-SHA256 digest over the raw HTTP request body. To confirm a request is genuine, the receiving server has to recompute that digest over the same bytes. If a global JSON body parser has already consumed and deserialized the body before the handler runs, those original bytes are no longer available.

Why re-serializing never matches

HMAC operates on bytes, not on parsed data structures. Once middleware turns the payload into a JavaScript object and the handler re-serializes it, the resulting string is rarely byte-for-byte identical to what the sender produced. As the dev.to article explains, small differences — spacing between keys and values, which characters get escaped, and how numbers are formatted, including scientific notation — are enough to change the input to SHA-256 and therefore the final digest. The signature comparison fails even though the webhook itself is completely legitimate.

How a correct Stripe check works

The article's reference implementation for Stripe follows a sequence the author presents as the safe pattern:

  • Parse the signature header into its parts, extracting the timestamp field and the v1 digest.
  • Reject the request if the timestamp is missing, not an integer, or further from current server time than a configurable tolerance, which the author defaults to 300 seconds to bound replay attacks.
  • Rebuild the signed payload as the timestamp, a literal dot, and the raw body read as UTF-8.
  • Compute an HMAC-SHA256 of that string using the webhook secret.
  • Compare the computed digest against the one supplied in the header, first checking that the lengths match and then using crypto.timingSafeEqual to avoid timing side channels.

Shopify's webhooks are authenticated the same way in kind — an HMAC over the raw body — and the article treats the two providers' failure modes as identical.

Getting at the raw body

The remedy is architectural: make sure the unparsed byte buffer survives until verification. Common approaches in the Node ecosystem include exempting webhook routes from the global JSON middleware, mounting a parser on those routes only, or using the parser's verify callback to stash the raw buffer on the request object. Verification then runs against the buffer, and only after it passes is the payload parsed into an object for business logic.

The dev.to post also links an interactive sandbox for experimenting with Stripe and Shopify HMAC signatures.

Why it matters

Webhooks carry the events that settle payments, update fulfillment, and change subscription state. When verification silently fails because the code hashed the wrong bytes, those events get dropped or endlessly retried, and teams are often tempted to disable signature checks altogether — which would let anyone who can reach the endpoint forge a webhook and, for example, mark an invoice as paid without a real charge.

The underlying lesson generalizes well beyond these two providers: an HMAC is a contract over bytes, so any layer that normalizes, reformats, or re-encodes the body before verification invalidates it. With JSON-parse-first middleware still the default in many Express and serverless setups, this remains one of the easiest security bugs to introduce and one of the cheapest to fix once you know to look for it.

  • #node-js
  • #typescript
  • #webhooks
  • #security
  • #stripe
  • #shopify

Related posts