Find webhook handlers that never verify the signature — before they ship.
A webhook endpoint that doesn't verify its signature will accept any POST that
reaches the URL. An attacker who finds the endpoint can forge events — a fake
checkout.session.completed, a fake payment_intent.succeeded — and your app
will act on them. It's a high-severity bug, it's easy to introduce, and, as
every guide notes, most developers skip the check.
There's even a live CVE for the adjacent mistake — an empty signing secret
(CVE-2026-41432).
hookseal is a zero-config gate that scans your code for webhook receivers and
flags the ones that don't verify the signature.
$ hookseal .
● 1 forgeable webhook(s):
src/routes/stripe.js:14 Stripe webhook received but the signature is never verified.
↳ This endpoint accepts any POST that reaches it — an attacker can forge Stripe
events. Verify the signature before trusting the payload.
[HS001 Stripe]
1 blocker · 0 warnings
Exit code 1 when it finds a forgeable webhook, so it drops straight into
pre-commit or CI.
The detection is simple and language-agnostic. A provider's signature header
(Stripe-Signature, X-Hub-Signature-256, X-Slack-Signature,
X-Shopify-Hmac-Sha256, X-Twilio-Signature, …) is a high-signal marker that a
file receives that provider's webhook — that header is essentially never
mentioned for any other reason. If such a file contains none of the known
verification primitives (the provider's SDK verifier, or a raw HMAC compare like
hmac.compare_digest / crypto.timingSafeEqual), the webhook is forgeable.
No code is executed, no network calls are made, and there's nothing to configure. It's a single static binary.
Providers covered: Stripe, GitHub, Shopify, Slack, Twilio, Svix, Square — plus any provider verified with a raw HMAC.
go install github.com/jay-tank/hookseal@latestOr build from source: go build -o hookseal .
hookseal # scan the current directory
hookseal ./src # scan a path
hookseal --strict # treat warnings (e.g. empty secret) as failures too
hookseal --json # machine-readable output- run: go run github.com/jay-tank/hookseal@latest ./ --strictExit codes: 0 clean · 1 a forgeable webhook (or any finding under --strict)
· 2 usage error.
| Rule | Severity | What |
|---|---|---|
| HS001 | blocker | A provider webhook is received but the signature is never verified |
| HS003 | warning | A webhook signing secret is set to an empty string (verification becomes a no-op) |
hookseal is a heuristic gate: it looks at each file on its own. If you verify a
signature in shared middleware (a different file), add a hookseal:ignore
comment anywhere in the handler file, or list a path substring in a
.hooksealignore file (one per line, # for comments).
hookseal answers one question well — "does this webhook receiver verify its signature at all?" — across languages, as a fast static gate. It does not prove your verification is correct (right secret, constant-time compare, raw body before parsing); it proves that a check is present. That single question is the one that catches the most common and most dangerous mistake: no check at all.
MIT © Jay Tank