DEV Community

RevClamp Enterprise
RevClamp Enterprise

Posted on Originally published at revclamp.com

Stripe and Shopify Webhook HMAC-SHA256 Verification: Why JSON Body Parsers Break Raw-Body Signatures

The most frequent cause of 401 Unauthorized webhook failures in Node and TypeScript backends is verifying the HMAC-SHA256 signature against JSON.stringify(req.body) instead of the exact raw UTF-8 byte stream transmitted by Stripe or Shopify.

Whenever a global JSON body parser deserializes and re-serializes a payload, subtle differences in key spacing, escaped characters, or scientific notation produce a completely different SHA-256 digest.

import crypto from 'node:crypto';

export function verifyStripeWebhookRawHmac({ rawBodyBuffer, sigHeader, secret, nowSec, toleranceSec = 300 }) {
  const parts = Object.fromEntries(
    String(sigHeader || '').split(',').map((kv) => kv.trim().split('='))
  );
  const ts = Number(parts.t);
  if (!Number.isInteger(ts) || Math.abs(nowSec - ts) > toleranceSec) return false;
  const signedPayload = `${ts}.${rawBodyBuffer.toString('utf8')}`;
  const expected = crypto.createHmac('sha256', secret).update(signedPayload, 'utf8').digest();
  const provided = Buffer.from(String(parts.v1 || ''), 'hex');
  return expected.length === provided.length && crypto.timingSafeEqual(expected, provided);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)