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:

FieldWhy it matters
idStable invoice identifier. Use this to dedupe across multiple invoice.payment_failed events.
customerStripe customer ID. Join to your user record to find their email and account state.
subscriptionSubscription ID. Useful to look up plan, MRR, and customer lifetime value for prioritization.
attempt_countWhich retry this is (1 = first failure). Drives email cadence.
next_payment_attemptUnix timestamp for next Smart Retry. null = retries exhausted.
hosted_invoice_urlFresh recovery link. Always pull from the latest event, not cached.
amount_dueDunning emails should mention the amount so the customer recognizes the charge.
last_finalization_error / last_payment_errorFailure 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?
Stripe sends invoice.payment_failed every time an attempted charge against an invoice fails. On the initial attempt and on every subsequent retry attempt. For a typical Smart Retries configuration, you can expect 3-4 invoice.payment_failed events per failed subscription invoice before Stripe gives up.
What's in the invoice.payment_failed payload?
The event's data.object is an Invoice. Useful fields: id, customer, subscription, attempt_count, next_payment_attempt (unix timestamp or null when retries are exhausted), hosted_invoice_url (fresh recovery link), amount_due, currency, and last_finalization_error or last_payment_error for the failure reason. Always read attempt_count to know which retry you're on.
How is invoice.payment_failed different from charge.failed?
charge.failed fires for any failed Charge, including one-time payments. invoice.payment_failed is subscription-aware. It only fires for invoice-driven charges (subscriptions and metered billing). For SaaS dunning, you almost always want invoice.payment_failed, not charge.failed.
Should I send an email on every invoice.payment_failed event?
No. Stripe Smart Retries fires invoice.payment_failed multiple times per failed invoice (one per retry attempt). Emailing the customer on every retry is harassment. The right pattern: dedupe by invoice ID, send your first dunning email after the first failure, then space follow-ups by hours/days, not by retry events.
What does next_payment_attempt = null mean?
It means Stripe has exhausted its Smart Retries and won't try the charge again. This is your signal to either trigger your final dunning email (last call) or cancel the subscription, depending on your dunning policy. The invoice is now uncollectible without manual intervention.
How do I get a fresh hosted invoice link?
The hosted_invoice_url field in the invoice payload is the fresh link. Stripe rotates the token, so always use the URL from the most recent event, not a cached one from a previous webhook. If your customer clicks a 5-day-old link from your first dunning email, it'll likely 404.
What's a common bug in invoice.payment_failed handlers?
Two common bugs: (1) using the invoice ID from your DB instead of the fresh hosted_invoice_url from the latest event, so customers get expired links; (2) not deduping events, so customers get 4 'your payment failed' emails on day 1 because Smart Retries fired 4 times. Both look like bugs to the customer and tank conversion.
How does ChurnNote handle invoice.payment_failed?
ChurnNote registers a webhook on your Stripe account, dedupes invoice.payment_failed events by invoice ID, and runs a 3-email dunning sequence at T+0/T+48h/T+120h. Every email pulls a fresh hosted_invoice_url at send time. The sequence stops automatically when invoice.payment_succeeded fires.

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