Webhook endpoints that process payloads without verifying the sender's signature accept forged requests from anyone who knows the endpoint URL. An attacker can POST a fabricated payment_succeeded event to trigger order fulfillment without paying, or a user.deleted event to wipe accounts. CWE-345 (Insufficient Verification of Data Authenticity) and CWE-347 (Improper Verification of Cryptographic Signature) define this failure. OWASP A02 (Cryptographic Failures) covers the broader category. NIST 800-53 SC-8 requires data integrity protection in transit — webhook signature verification is the enforcement mechanism for inbound event streams.
Medium because exploitation requires knowing the webhook URL, but the business impact of forged events (fake payments, account manipulation) can be severe and irreversible.
Verify the webhook signature before processing any payload. Read the body as raw text before parsing — webhook providers sign the raw bytes, and parsing first invalidates the signature.
// app/api/webhooks/stripe/route.ts
export async function POST(req: NextRequest) {
const body = await req.text(); // Raw text — not req.json()
const signature = req.headers.get('stripe-signature');
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature!, process.env.STRIPE_WEBHOOK_SECRET!);
} catch (err) {
return NextResponse.json({ error: 'Signature verification failed' }, { status: 400 });
}
// Safe to process event.type
}
Every major webhook provider (Stripe, Clerk, GitHub, Svix) offers a signature mechanism — use the provider's official SDK method rather than rolling your own HMAC check.
ID: saas-authorization.api-auth.webhook-validates-sender
Severity: medium
What to look for: Count all relevant instances and enumerate each. Find webhook receiver endpoints — routes like /api/webhooks/stripe, /api/webhooks/clerk, /api/webhooks/github, or any endpoint whose name or comments suggest it receives external webhook payloads. Check whether the handler verifies a signature, HMAC, or shared secret before processing the payload. Look for stripe.webhooks.constructEvent, crypto.createHmac, svix.verify, or similar verification calls.
Pass criteria: All webhook endpoints verify the sender's identity using a cryptographic signature (HMAC, provider SDK verification) before processing the event payload. At least 1 implementation must be verified.
Fail criteria: Any webhook endpoint processes the incoming request body without first verifying the sender's signature. This allows an attacker to send fake webhook payloads (e.g., fake payment confirmations) to the endpoint.
Skip (N/A) when: No webhook receiver endpoints detected in the codebase.
Detail on fail: "Webhook handler at [route] processes payloads without signature verification. Attackers can send forged events." (Note the specific webhook provider and route.)
Remediation: Always verify the webhook signature before processing. Every major webhook provider offers a signature mechanism.
// app/api/webhooks/stripe/route.ts
export async function POST(req: NextRequest) {
const body = await req.text(); // Must be raw text, not parsed JSON
const signature = req.headers.get('stripe-signature');
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature!, process.env.STRIPE_WEBHOOK_SECRET!);
} catch (err) {
return NextResponse.json({ error: 'Signature verification failed' }, { status: 400 });
}
// Now safe to process event.type
}
The raw body must be read as text before parsing — most webhook signature systems sign the raw bytes, not the parsed JSON. Using req.json() before verification will cause the verification to fail.