Webhook signature verification for Node.js

doorbell-js HMAC the raw bytes Stripe, GitHub, Slack, Shopify, Clerk, and Svix actually signed. Not JSON.parse. Not JSON.stringify.

npm i doorbell-js

The Next.js App Router export is the route. Do not call req.json().

import { doorbell } from 'doorbell-js'

export const POST = doorbell({
  stripe: {
    secret: process.env.STRIPE_WEBHOOK_SECRET,
    on: {
      'checkout.session.completed': async (event) => {
        await fulfill(event.payload)
      },
    },
  },
})

Fix the error you pasted into Google

What it checks

HMAC first, then the clock, the way stripe-node does. Missing headers still burn HMAC so that path is not faster. GitHub and Shopify do not get event.type from unsigned headers. Compare digest bytes, not hex strings. Express does not trust X-Forwarded-Host for the Twilio URL. http is only localhost. Transfer-Encoding: chunked and a mismatched Content-Length are refused. Replies are Cache-Control: no-store.

Providers: Stripe, GitHub, Slack, Shopify, Svix, Clerk, Resend, Linear, Paddle, Meta, Twilio.

MIT. Source and README.