@octokit/webhooks-methods

Sign and verify GitHub webhook payload signatures with constant-time HMAC comparison, in Node.js and the browser.

SDK
npm
v6.0.0
27stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
51/100Fair
Development Activity60
Maintenance48
Community24
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
72/100Good
Architecture78
Code Quality88
Innovation72
Learning Curve50

@octokit/webhooks-methods is a small, focused utility from the Octokit family that implements the exact cryptographic contract GitHub uses to sign webhook deliveries: HMAC SHA-256 over the raw payload string, expressed as a sha256=<hex> signature header. It exposes three functions — sign() to produce a signature, verify() to check one against a secret, and verifyWithFallback() to check a signature against a primary secret and a list of additional secrets in turn, which is what makes zero-downtime webhook secret rotation possible without hand-rolling the loop yourself.

The library ships two implementations from one TypeScript source tree: a Node.js build using node:crypto (createHmac, timingSafeEqual) and a browser/Deno-compatible build using the Web Crypto SubtleCrypto API, built separately via esbuild so neither runtime pulls in APIs it doesn’t have. Signature comparison is always constant-time to prevent timing attacks, and every function throws a clearly-prefixed TypeError when required arguments are missing or the payload isn’t a raw string — a common webhook-handling mistake when payloads have already been JSON-parsed.

What You Get

  • sign(secret, payload) — produces a sha256=<hex> HMAC signature string for a raw payload string
  • verify(secret, payload, signature) — constant-time comparison of a computed signature against the one GitHub sent
  • verifyWithFallback(secret, payload, signature, additionalSecrets) — tries the primary secret then each fallback secret in turn, enabling zero-downtime secret rotation
  • Dual Node.js (node:crypto) and browser/Deno (SubtleCrypto) builds compiled from one TypeScript source tree via esbuild
  • Strict input validation — every function throws a descriptive TypeError for missing arguments or a non-string payload instead of silently miscomparing

Common Use Cases

  • Verifying inbound GitHub App or webhook deliveries before trusting the payload in an Express, Fastify, or serverless handler
  • Rotating a webhook secret in production without downtime by passing the old secret into verifyWithFallback’s additionalSecrets list until every delivery source is updated
  • Signing synthetic webhook payloads in integration tests to exercise a service’s real signature-verification code path
  • Running the same verification logic in an edge/browser runtime (e.g. a Cloudflare Worker) via the SubtleCrypto build, without pulling in Node’s crypto module

Under The Hood

Architecture The package is a stateless utility library, not a service or framework, so its architecture is about clean platform separation rather than layering: src/node/sign.ts and src/node/verify.ts implement the public API with node:crypto, src/web.ts re-implements the identical sign/verify/verifyWithFallback contract with the Web Crypto SubtleCrypto API for browser and Deno consumers, and src/index.ts is the Node-facing entry point that re-exports the node implementation plus a shared verifyWithFallback. scripts/build.mjs drives esbuild to produce three separate output bundles (dist-src, dist-node, dist-web) from these entry points, so a browser bundler never sees a node:crypto import and a Node consumer never pulls in SubtleCrypto polyfill code. There’s no dependency injection or complex data flow to speak of — correctness here means both implementations staying behaviorally identical, which the shared test fixtures in test/common.ts and duplicated test suites (Node via vitest, browser via Puppeteer, Deno via deno test) exist specifically to guarantee.

Tech Stack Written in TypeScript targeting Node >= 20, using node:crypto’s createHmac/timingSafeEqual for the server build and the Web Crypto crypto.subtle API for the browser/Deno build. Built with esbuild (scripts/build.mjs) into ESM output, type-checked separately via tsc against @octokit/tsconfig. Tested with Vitest (@vitest/coverage-v8 for coverage, plus a vitest bench suite comparing sign/verify performance), Puppeteer for real-browser testing, and Deno’s built-in test runner for the Deno target. Formatting is enforced by Prettier (no ESLint) and releases are automated via semantic-release with the GitHub, npm, and commit-analyzer plugins, publishing from a pkg/ directory assembled by the build script.

Code Quality Test coverage is thorough for a library this size: test/sign.test.ts and test/verify.test.ts cover the happy path, every missing-argument error branch, wrong-type payloads, and a named regression test (issue #71) for control-character encoding edge cases across escaped and literal ANSI sequences. Error handling is explicit and consistent — every public function validates its own arguments and throws a TypeError prefixed with [@octokit/webhooks-methods] rather than allowing a downstream crash or a silent false result. Naming is minimal and consistent across the Node and web implementations, and CI runs the full matrix (Node, browser via Puppeteer, Deno) on every change, which is unusually rigorous cross-runtime verification for a package this small.

API Design The public surface is deliberately tiny — three functions, no configuration object, no class to instantiate — which matches exactly what a webhook consumer needs and nothing more. verifyWithFallback folds a whole secret-rotation pattern (try current secret, then try each previous one) into a single call instead of leaving every consumer to write that loop themselves, which is the one place this library adds real value beyond wrapping HMAC. VERSION is attached directly as a property on the exported sign/verify functions rather than requiring a separate import, a small ergonomic touch. It isn’t a novel cryptographic approach — HMAC-SHA256 with constant-time comparison is the standard, correct way to verify GitHub webhooks — but it packages that standard correctly and portably, which is exactly what a shared utility in this position should do.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search