Component · for humans & their agents
Webhook Signature Verify
verified · first-partyactively maintained$0 during beta (was $69)
Your webhook verifier has never been sent a bad request. Every real one arrives correctly signed, so the case that matters has never run.
by Code Recycle
Every claim on this page is refundable if it is untrue — refund policy.
Verified: 98 tests · 2/2 mutations caught · 1 adversarial review · verified by an independent reviewer, not the author
Fail-closed webhook signature verification for Stripe, GitHub, Slack, Shopify and Twilio, with constant-time comparison, a required replay window where the provider supports one, and machine-readable refusal reasons.
Fail-closed webhook signature verification for Stripe, GitHub, Slack, Shopify and Twilio, with constant-time comparison, a required replay window where the provider supports one, and machine-readable refusal reasons.
THE SILENT FAILURE. Signature verification is written once, tested against one good payload from the provider's docs, passes review, and then runs for years without ever being exercised by a bad request -- because in normal operation every request IS legitimate. The bug only appears when someone sends the request the happy path never covered, by which time the code has been trusted in production for a long time.
FAIL-OPEN ON A MISSING HEADER. A missing signature header is undefined; the comparison throws, a surrounding try/catch swallows it, or a falsy check short-circuits into the handler anyway. The endpoint 'verifies'. Every real request still works. This returns an explicit missing_signature_header verdict for every provider, exercised by a real test for each.
SUBSTRING MATCHING. Stripe's header carries multiple v1= values during secret rotation. header.includes(computed) returns true for a truncated or attacker-chosen substring, and reading only the first v1= rejects every legitimate request mid-rotation. This parses the header into discrete key -> value[] pairs and does a full-value constant-time comparison against every candidate.
THE WRONG BASE STRING. Every provider signs different bytes: Stripe signs timestamp.body, Slack v0:timestamp:body, GitHub and Shopify the body alone (hex and base64 -- mixing those is its own silent failure), Twilio the URL plus sorted form parameters. Getting the shape wrong fails loudly on day one. Getting the BODY wrong is the dangerous one: JSON.parse followed by JSON.stringify is not a byte-identical round trip, so a verifier fed a framework's re-serialised body fails intermittently and only for some payloads.
It distinguishes 'nobody signed this' from 'someone forged this' -- two different incidents that a boolean return value collapses into one.
VERIFIED: 98 tests, re-measured by running the suite. Every mutation was observed FAILING before the source was restored. Three behavioural tests (first-byte, last-byte, untampered) back the constant-time comparison, with two mutations proving they fail when it is weakened.
DELIVERY: signed download of a hash-verified tarball, immediately on purchase. Permissive licence: unlimited products, unlimited clients, unlimited seats, no attribution, perpetual and irrevocable. One restriction, do not republish the source as source.
Interface
What you call, and what comes back. Types and signatures only — the implementation ships with the source.
export function toBuffer(input: Uint8Array | string): Buffer;
export function concatBytes(...parts: Array<Buffer | string>): Buffer;
export function decodeHex(value: string): Buffer | null;
export function decodeBase64(value: string): Buffer | null;
export function constantTimeEqual(received: Buffer | null, expected: Buffer): boolean;
export function getHeader(headers: HeaderBag, name: string): string | undefined;
export function parseKvHeader(header: string): Map<string, string[]>;
export function hmacDigest(algorithm: "sha1" | "sha256", secret: string, data: Buffer): Buffer;
export function sha256Hex(data: Buffer): string;
export function verifyGithubSignature(rawBody: Uint8Array | string, headers: HeaderBag, secret: string): VerifyResult;
export function verifyShopifySignature(rawBody: Uint8Array | string, headers: HeaderBag, secret: string): VerifyResult;
export function verifySlackSignature( rawBody: Uint8Array | string, headers: HeaderBag, secret: string, opts: SlackVerifyOptions = {}, ): VerifyResult; export type Provider = "stripe" | "github" | "slack" | "shopify" | "twilio";
export type HeaderBag = Record<string, string | string[] | undefined>;01Capabilities
Does
- + Authentication
- + Webhooks
- + Input validation
- + Webhook signature verification
Doesn’t
- No exclusions declared
02Requirements & stack
Depends on
No declared dependencies
Credentials needed
None declared
Stack
03Community
No endorsements yetNo verified confirmations yet — be the first.
Confirmations come from verified purchasers, installers, vetted reviewers, or an installation outcome your org reported through the agent tools. They grade quality — security is verified separately, and community votes can never override the security gate.
Sign in to confirm — weight comes from verified usage, not vote count.
Issues 1
Open an issue0 open · 0 answered · 0 fixed · 1 said it worked
- closedWorked for me — 98/98 vitest on Node 26.0.0, macOS 26.4Worked for me
04Trust Passport
Full passport →0/0 automated components pass. An automated score is never a security guarantee.
- publisher identity Publisher status verified; 1 verification(s) on file
- malicious pattern scan No known malicious-behavior patterns across 30 source file(s) plus listing text
- capability contract All 0 observed capability reference(s) match the declared manifest
- agent safety scan No injection patterns in agent-readable content
- provenance No release signature or provenance attestation
- behavioral sandbox Not performed in this environment — requires the production isolated runner (docs/sandbox-requirements.md). No untrusted code is ever executed on the application host.
Every listing must pass this review before it can be sold, and it is re-run on every release. Verification describes what we checked — it is not a guarantee that the software is safe.
05Versions
Full history →| Version | Channel | Released | Notes |
|---|---|---|---|
| 1.0.0 | stable | Aug 5, 2026 | First public release. |