FixVibe

// code / spotlight

Webhook Signature Verification

If your webhook handler doesn't verify the signature, anyone can forge events.

The hook

Webhook signature verification is one of the most consistently-skipped steps in integration code, and the reason is structural: the integration works fine without it. The webhook arrives, the body parses, the handler runs, the test passes. Verification is the step you only notice when an attacker posts a forged event to your endpoint. Stripe, GitHub, Clerk, Resend, Linear, Slack, Vercel, and every other service worth integrating with sets a signature header β€” `Stripe-Signature`, `X-Hub-Signature-256`, `svix-id`, etc. β€” and explicitly tells you to verify before trusting the payload. The instructions are in the documentation. The skipping is in the codebase.

Auala e galue ai

Each provider computes an HMAC over the raw request body (using a secret you configure on their side), serializes the result into a header, and sends both. Your handler is supposed to: read the raw body (not the parsed body β€” parsing changes whitespace and breaks the HMAC), recompute the HMAC using your shared secret, compare with the header using a timing-safe equality function, then trust the body. Skip the check and you accept any payload from any caller β€” Stripe's webhook URL is not authenticated by IP, secret token in URL, or any other mechanism; the signature is the only authentication. The mistakes cluster in three shapes: never adding the verification call (the handler trusts everything), parsing the body before verifying (the HMAC fails because the parsed-and-reserialized body has different whitespace), and using non-timing-safe comparison (`===` against the signature is technically right but timing-leaks the secret bit-by-bit).

The variants

No signature check at all

Handler parses and trusts every incoming POST. Most common shape; trivially exploitable by anyone who knows the URL.

Verify-after-parse

Handler parses body to JSON, then computes HMAC over the JSON.stringify version. Whitespace differs from the original; HMAC fails. Verification 'works' for legit events because both sides round-trip the same way, but is broken for production-grade attackers.

Non-timing-safe comparison

`if (signature === expectedSignature)` instead of `crypto.timingSafeEqual(...)`. Leaks one bit per request through timing analysis. Less common in practice but a genuine vulnerability.

Replay without timestamp check

Even with HMAC verification, accepting old timestamps lets an attacker replay captured events. Stripe's signature includes a timestamp; rejecting events older than 5 minutes defeats replay.

The blast radius

Forged events. Stripe webhook fakery: an attacker posts a fake `payment_intent.succeeded` event to your `/api/stripe/webhook` endpoint, your handler marks the order as paid, you ship goods you weren't paid for. GitHub webhook fakery: fake `release.published` event triggers your CI to deploy attacker-controlled artifacts. Clerk webhook fakery: fake `user.created` event seeds a malicious user into your database with admin role. The pattern is identical across providers β€” without signature verification, the webhook endpoint is an unauthenticated mutation API.

// what fixvibe checks

What FixVibe checks

FixVibe repo scans look for high-confidence security patterns and dependency risk in source context. Reports identify the affected area and recommended fix. For check-specific questions about exact detection heuristics, active payload details, or source-code rule patterns, contact support@fixvibe.app.

Ironclad defenses

Always verify signatures before parsing or trusting any body field. Use the official SDK helper when available β€” `stripe.webhooks.constructEvent(rawBody, sig, secret)` for Stripe, GitHub's documented HMAC SHA-256 over rawBody compared with `crypto.timingSafeEqual`, `svix.verify(rawBody, headers)` for Svix-shaped providers (Resend, Clerk, Linear). Critical: read the raw body before parsing β€” `req.text()` in Next.js, `bodyParser.raw()` in Express, `express.raw({ type: '*/*' })` middleware, or framework-specific raw-body access. Reject requests without signatures, even in test environments; sloppiness in test becomes sloppiness in prod. Validate the timestamp included in the signature (Stripe's tolerance is 5 minutes by default) so old captured events can't be replayed. Store webhook event IDs after processing and refuse re-processing the same ID β€” handles legitimate retries without enabling deliberate replay. As a final layer, scope the webhook secret per-provider per-environment so a leak is contained.

The takeaway

Signature verification is the difference between a webhook endpoint that's part of a trusted integration and one that's a public mutation API. The verification call is one line. Skipping it is the kind of bug you find in a postmortem rather than a code review.

// run it on your own app

Keep shipping while FixVibe keeps watch.

FixVibe pressure-tests the public surface of your app the way an attacker would β€” no agent, no install, no card. We keep researching new vulnerability patterns and turn them into practical checks and paste-ready fixes for Cursor, Claude, and Copilot.

Source code
52
tests fired in this category
modules
14
dedicated source code checks
every scan
384+
tests across all categories
  • Free β€” no credit card, no install, no Slack ping
  • Just paste a URL β€” we crawl, probe, and report
  • Severity-graded findings, deduped to signal only
  • Current, AI-ready fix prompts you can paste into Cursor, Claude, Copilot
Run a free scan β†’

// latest checks Β· practical fixes Β· ship with confidence

Webhook Signature Verification β€” Vulnerability Spotlight | FixVibe Β· FixVibe