webhook-hmac-kit
Webhook signing and verification that hashes the raw bytes. Standard Webhooks and Stripe-style formats, zero runtime dependencies, a Web Request adapter and Hono middleware, and a shipped test-vector corpus.
2026oss#web
- live npmjs.com
- source github.com
- blog /blog/two-bodies-one-valid-webhook-signature
Overview
webhook-hmac-kit verifies incoming webhooks over the raw request bytes, never over a re-serialised body, which is the bug the companion post is about. It supports the Standard Webhooks format (webhook-id, webhook-timestamp, webhook-signature, whsec_ secrets) with a timestamp tolerance window and key rotation, and Stripe-style signatures. When you control both ends it also signs and verifies in its own v2 format with nonce-based replay protection. Runs on Web Crypto only, so Node, Cloudflare Workers, Deno and Bun behave the same with no bundler configuration. Version 2.3.0 (25 Oct 2026) added a Web Request adapter for Next.js route handlers, Workers, Deno and Bun, plus Hono middleware, alongside the existing Express, Fastify and NestJS adapters. The package has zero runtime dependencies, ships its test-vector corpus as JSON (npx webhook-hmac-kit vectors prints or verifies it), and is published to npm with provenance from GitHub Actions.
Stack
TypeScript · Node.js · Web Crypto · Security · Open Source
Challenges
- Two different request bodies can share one valid signature when the canonical string is not injective; the library has to hash bytes, not reconstructed JSON
- Node, Workers, Deno and Bun expose different request primitives, and a body can only be read once
- Keeping the verification result safe to log: a failed verification must not leak why it failed to the caller's logs by accident
Solutions
- Every verifier reads the body once, as bytes, and the adapters return the verified payload rather than letting the handler re-read the stream
- One Web Request function per scheme returns { ok, payload } or { ok, status, response } with a ready JSON Response, so the same code runs on every runtime
- The error field on a failed result is non-enumerable, so serialising the result does not leak it
- Known-answer vectors for both schemes ship in the package and the CLI can run any corpus file through the library
Outcome
Published on npm with provenance, v2.3.0 released 25 Oct 2026