## TL;DR

For failed subscription or invoice payments, handle invoice.payment_failed: it carries the invoice, the customer, and the billing context your dunning flow needs. charge.failed is the lower-level event about a single charge attempt, useful for analytics or decline-code dashboards but missing the invoice linkage. Handling both usually double-counts. Pick invoice.payment_failed as the recovery trigger and subscribe to charge.failed only for payment-ops telemetry.

## The query

```text
Stripe "invoice.payment_failed" vs "charge.failed": which webhook to handle
```

## Use this when

- Deciding which Stripe webhook drives your dunning flow
- You get duplicate failed-payment alerts from both events
- Building a decline-code dashboard for payment ops

## Not for

- Successful payments (use invoice.paid / invoice.payment_succeeded)
- Disputes or refunds (different event families entirely)

## Steps

1. Subscribe to invoice.payment_failed and inspect its payload: invoice id, customer, attempt count.
   Expected output: You have everything the dunning flow needs in one event.
2. Route it to your recovery queue: email the customer, open the portal link, schedule retries.
   Expected output: Failed invoices enter recovery automatically.
3. If you want decline-code telemetry, also listen to charge.failed and join on payment_intent.
   Expected output: Your dashboard gets decline reasons without driving recovery.
4. Dedupe: do not send customer emails from both handlers.
   Expected output: Customers get one recovery email, not two.
5. Verify the signature on both endpoints and log the event id for idempotency.
   Expected output: Replays do not double-trigger the flow.

## Variant phrasings

### Stripe invoice.payment_failed vs charge.failed

### which webhook for failed subscription payment Stripe

### Stripe dunning webhook invoice payment failed

## Root cause

invoice.payment_failed sits at the billing layer and charge.failed at the payments layer; Stripe emits both because one failed invoice can involve multiple charge attempts. Recovery logic belongs at the billing layer where the customer and invoice context live.

## Edge cases

- Smart retries can emit several charge.failed events for one invoice.payment_failed; count at the invoice level
- invoice.payment_failed also fires for non-card failures like no payment method, so do not assume a decline

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_AH_-vdHJyS2viUoL8C6NNQ
