# Webhook Endpoints Validate Sender

- **Pattern:** `ab-002236` (`saas-authorization.api-auth.webhook-validates-sender`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002236
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002236)

## Why it matters

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.

## Severity rationale

Medium because exploitation requires knowing the webhook URL, but the business impact of forged events (fake payments, account manipulation) can be severe and irreversible.

## Remediation

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.

```typescript
// 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.

## Detection

- **ID:** `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.

  ```typescript
  // 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.

## External references

- cwe CWE-345
- cwe CWE-347
- owasp A02
- nist SC-8

Taxons: access-control, cryptography-and-secrets

HTML version: https://auditbuffet.com/patterns/ab-002236
