Stripe webhook reference
Stripe invoice.payment_failed
The webhook that fires every time a subscription charge fails on Stripe. Handle it correctly and you recover 20-30% of involuntary churn. Handle it wrong and you spam customers with stale links.
Quick answer
invoice.payment_failed fires on every failed retry attempt for a subscription invoice. Dedupe by invoice ID, use the fresh hosted_invoice_url from the latest event, and send a 3-email dunning sequence (not one email per retry). Stop when invoice.payment_succeeded fires or next_payment_attempt becomes null.
What the event means
invoice.payment_failedis Stripe's notification that an automatic charge against a subscription invoice has failed. It fires on the initial failure and on every Smart Retry attempt that also fails. If Smart Retries are configured, expect roughly 3-4 events per failed invoice over a ~3 week window before Stripe stops trying.
Useful fields in the payload
The event's data.object is an Invoice. The fields that drive a working dunning flow:
| Field | Why it matters |
|---|---|
| id | Stable invoice identifier. Use this to dedupe across multiple invoice.payment_failed events. |
| customer | Stripe customer ID. Join to your user record to find their email and account state. |
| subscription | Subscription ID. Useful to look up plan, MRR, and customer lifetime value for prioritization. |
| attempt_count | Which retry this is (1 = first failure). Drives email cadence. |
| next_payment_attempt | Unix timestamp for next Smart Retry. null = retries exhausted. |
| hosted_invoice_url | Fresh recovery link. Always pull from the latest event, not cached. |
| amount_due | Dunning emails should mention the amount so the customer recognizes the charge. |
| last_finalization_error / last_payment_error | Failure reason. Useful for picking the right email copy. |
Common bugs in invoice.payment_failed handlers
- Sending an email on every event. Smart Retries fires 3-4 invoice.payment_failed events per failed invoice. Without dedupe, the customer gets 4 nearly identical emails in a week.
- Caching the hosted_invoice_url. Stripe rotates the token. A 5-day-old link in your first dunning email will likely 404. Always render the link from the latest event.
- Cancelling on the first failure. Roughly 40% of failed charges recover on their own via Smart Retries. Cancelling immediately throws away recoverable revenue.
- Ignoring next_payment_attempt = null.That's your last-call signal. After that, Stripe won't retry and you need to either send a final email or cancel cleanly.
- Forgetting invoice.payment_succeeded.If the retry succeeds mid-dunning, you need to stop the email sequence. Otherwise the customer who already paid gets a "your payment failed" reminder.
FAQ
When does Stripe send invoice.payment_failed?▼
What's in the invoice.payment_failed payload?▼
How is invoice.payment_failed different from charge.failed?▼
Should I send an email on every invoice.payment_failed event?▼
What does next_payment_attempt = null mean?▼
How do I get a fresh hosted invoice link?▼
What's a common bug in invoice.payment_failed handlers?▼
How does ChurnNote handle invoice.payment_failed?▼
Skip the webhook plumbing.
ChurnNote handles invoice.payment_failed end-to-end. Dedupe, fresh links, 3-email cadence, automatic stop on success. $12/mo flat.
Get started