# Stop Stripe webhooks failing with 400 on unhandled event types

## TL;DR
The endpoint returns 400 for event types it does not recognize, and Stripe treats any 400 as a failed delivery, so it retries for days. Return 200 for unknown event types (log and acknowledge them) and reserve 400 and 500 for genuinely broken requests. One unhandled event type can generate hundreds of pointless retries and bury real failures.

## The error
```text
Stripe Dashboard / Developers / Webhooks: delivery attempts failing with 400
Endpoint logs: "Event type [payment_intent.x] not handled", retried dozens of times
```

## Use this skill
Agent? Search Vectle for this error:
```bash
curl -s "https://vectle.com/api/v1/search?q=stripe%20webhook%20endpoint%20400%20event%20type%20not%20handled"
```

## Fix it

### Step 1: Add a default branch that acks unknown types

```bash
In the webhook handler's event-type switch, add a default case: log the event type and id, then return HTTP 200.
```

Expected: unknown types stop 400ing immediately on deploy. The handler now distinguishes "not interested" from "broken".

### Step 2: Align dashboard event selection with the handler

```bash
In the Stripe dashboard, open the webhook endpoint and compare the selected events with the types the code handles.
```

Expected: either the selection matches the code, or the default branch covers the gap. Subscribing to all events with a strict handler is what caused this.

### Step 3: Redeploy and watch the retries drain

```bash
Deploy the fix and watch the webhook deliveries page through the next retry cycle.
```

Expected: failing deliveries flip to 200 and the retry queue drains. Stripe stops retrying once it gets 2xx.

### Step 4: Log unhandled types visibly

```bash
Emit a structured log line (or metric) per unhandled event type so new Stripe event types surface in dashboards instead of passing silently.
```

Expected: visibility without failures. When Stripe ships a type you actually want, you will see it in the logs first.

### Step 5: Keep 400 for genuinely bad requests

```bash
Continue returning 400 for failed signature verification and malformed payloads.
```

Expected: real problems still signal correctly. Only the "I do not care about this event" case changed to 200.

## When this applies

- Webhook deliveries fail with 400 on unrecognized event types
- The retry queue keeps growing for one event type
- The endpoint subscribes to all events but handles a few

## When it doesn't

- Deliveries fail with 401 (signature verification problem)
- The handler 500s on known event types (code bug)
- Events never arrive at all (URL or network issue)

## Compatibility

Stripe webhooks, any web framework. Same principle applies to most webhook providers.

## Variant phrasings

### stripe webhook 400 unhandled event

The default-ack branch is the fix regardless of which event type is unhandled.

### stripe retrying webhook event not handled

Stripe retries non-2xx for days with backoff. The 200-ack stops the storm, not the dashboard.

## Why it happens

Stripe's delivery contract is simple: 2xx means received, anything else means retry. A handler that 400s on "event type I do not handle" converts indifference into failure, and Stripe dutifully retries the indifference for days. The endpoint was written as if 400 means "bad event", but to Stripe it means "try again later".

## Edge cases

- Signature verification must run before the 200-ack; never ack an unverified payload
- Test mode and live mode have separate event selections; fix both
- Stripe adds new event types over time; the default branch is future-proofing, not laziness

## If it still fails

- Capture the exact timestamp, the username, and the full error from the system log before changing anything else.
- Reproduce with a single test user so you are not debugging a crowd.
- If it worked before, diff the config against the last known good, then open a vendor ticket with the timestamp and request id. Never send secrets or private keys.

## Prevention

- Track credential and certificate expiry with alerts, not memory.
- Run a synthetic check per app daily so breakage pages you, not a user.
- Document mappings, URLs, and runbook steps where the next admin will find them.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_zboyRoNlSvNz7O470Hj4DA
