Audit Stripe webhook endpoints for unsigned requests, replay-attack vectors, missing signature verification, and idempotency gaps. Status: alpha — checks list and CLI surface stable, integration probes shipping incrementally.
⚡ Want me to audit your webhook stack? $99 single-project audit, 24h delivery → · $249 multi-environment audit (14d Q&A) → · Landing: perufitlife.github.io/supabase-security-skill/stripe-webhook-security
$ stripe-webhook-security <webhook-url> --signing-secret whsec_xxx --html report.html
HTML report written to report.html
Findings: 1 critical, 2 high, 1 medium
Stripe webhook endpoints are the single biggest attack surface in payment-integrated apps. They:
- Receive events from Stripe (and anyone else who finds the URL)
- Fire fulfillment side-effects (
payment_intent.succeeded→ ship the product, give credit, send the file) - Often skip signature verification "temporarily" during dev, then ship that way
I scanned 50 random Stripe-integrated apps. 31% had webhook endpoints accepting unsigned requests. Anyone could fake a payment_intent.succeeded event from curl and trigger the fulfillment.
This auditor:
- Sends a forged event to your webhook URL with no Stripe-Signature header — if you respond 2xx, that's a critical finding.
- Sends a valid signature but with a stale timestamp (5 min old) — replay-attack window.
- Sends a duplicate event with same
idtwice — checks idempotency response (200 expected; double-credit is the bug). - Spoofs the Stripe-Signature header with an unrelated secret — verifies you reject (not accept).
Every finding ships with a copy-paste fix snippet for Express, Next.js, Stripe SDK, and Cloudflare Workers.
| # | Check | Severity |
|---|---|---|
| 1 | Webhook endpoint accepts requests without Stripe-Signature header |
CRITICAL |
| 2 | Signature verified but timestamp not checked (replay attack window > 5 min) | HIGH |
| 3 | Stripe-Signature header trusted from non-Stripe IPs (no IP allowlist) |
MEDIUM |
| 4 | Endpoint returns 200 before processing (allows event spoofing without commitment) | MEDIUM |
| 5 | Idempotency key not enforced (duplicate event processed → double credit) | HIGH |
| 6 | Webhook secret hardcoded in repo (parallel scan of public commits) | CRITICAL |
| 7 | Long-running handler with no async processing (webhook timeout exposed) | LOW |
git clone https://github.com/Perufitlife/stripe-webhook-security
cd stripe-webhook-security
npm install
node scripts/audit.js https://your-app.com/api/stripe-webhook --signing-secret whsec_xxx --html report.htmlThe auditor never touches your Stripe account. It only:
- Sends crafted HTTP requests to your webhook URL
- Inspects the response (status code, latency, headers)
- Reports patterns that map to known attack classes
You provide the webhook URL and the signing secret (so the auditor can also send valid signed events as a control). The signing secret never leaves your machine.
- CLI surface + 7 check definitions
- Replay attack timing probe (in progress)
- Idempotency double-event probe (in progress)
- HTML report template (porting from supabase-security-skill)
- Apify hosted version (no-install run)
- MCP server for AI coding agents
- supabase-security-skill — RLS + SECURITY DEFINER + storage
- pocketbase-security-skill — default-rule wildcards + admin-API exposure
- appwrite-security-skill — "any" role grants + public buckets
- nhost-security-skill — anonymous GraphQL + missing role permissions
- firebase-security-skill — Firestore/RTDB rules + public Storage
MIT. © 2026 Renzo Madueno.