- To read it and understand the event: nothing
- Only for the optional live part: a Stripe account in test mode and the Stripe CLI installed
Introduction
Whenever something relevant happens in your account — an invoice is charged, a card is declined, a customer cancels — Stripe creates an event: a record of what happened, with all the related data. And it can send that to a web address of yours the moment it occurs.
Every event type has a name. The “could not charge” one for subscriptions is invoice.payment_failed, and it arrives with the affected invoice: which customer, how much, and often a link so they can pay.
In one line: the charge fails → Stripe sends it to you → your system reacts.
This guide only assumes you know that automatic HTTP notices exist. If that already sounds odd, start with Introduction to webhooks and come back.
Problem it solves
Without webhooks, you end up opening the Stripe Dashboard “just in case”. With an event, you can alert your team, open a ticket, or kick off a flow the moment the charge fails.
Use it when you bill subscriptions or recurring invoices and a card failure cannot wait until end of day.
The event that matters here
- Stripe tries to charge
- Payment fails
- It emits invoice.payment_failed
- Your URL receives the JSON
| Piece | Typical value | Why it matters |
|---|---|---|
type | invoice.payment_failed | Filter only failed charges |
data.object | invoice object | Customer, amount, status |
id of the event | evt_… | Unique identifier: use it so you never process the same notice twice |
livemode | false in test | Tells you whether the charge was real or simulated |
Useful fields on the invoice:
| Field | Use |
|---|---|
customer | cus_… id |
customer_email | Contact (when present) |
amount_due + currency | Amount in cents → e.g. 1900 + eur = 19.00 EUR |
hosted_invoice_url | Link for the customer to pay |
attempt_count | How many attempts so far |
Stripe docs: event types (opens in a new tab) · Invoice object (opens in a new tab).
Minimal JSON shape
Trimmed example (not a full dump):
{
"id": "evt_postnaut_demo_payment_failed",
"type": "invoice.payment_failed",
"livemode": false,
"data": {
"object": {
"id": "in_postnaut_demo",
"customer": "cus_postnaut_demo",
"customer_email": "demo@postnaut.test",
"amount_due": 1900,
"currency": "eur",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_demo/test_postnaut"
}
}
}That shape is what you will map to your destination (email, chat, CRM, queue…).
See a real event (no destination yet)
The JSON above is enough to know which fields to map. If you also want to see it live in your account (optional):
-
Open Stripe and turn on Test mode (the switch sits at the top of the Dashboard).
-
Install the Stripe CLI (opens in a new tab). It is a program you drive by typing commands in a terminal, and it lets you fire test events without simulating a real charge. Run
stripe loginonce to connect it to your account. -
In a terminal:
stripe trigger invoice.payment_failed -
Dashboard → Developers → Events: open
invoice.payment_failedand compare with the example (type,amount_due, etc.).
That proves Stripe emits the event. It does not yet prove your team alert works — that is the destination (another system).
If you do not want to install the CLI now, that is fine: the JSON shape in this guide matches what you will see later under Events.
Security
When you stand up the real receiver:
- HTTPS is required in production.
- The signing secret differs between test and live. Do not commit it.
- Stripe retries if you do not answer, so the same failed charge can reach you several times. Store the
evt_…of the ones you have processed and discard repeats; otherwise you will send four alerts for one unpaid invoice. - Answer quickly with a 200 and do the slow work afterwards.
Emitter vs destination
Two separate layers:
| Layer | What it proves | Where |
|---|---|---|
| Stripe emitter | Stripe delivers an event | Above (stripe trigger + Events) |
| Destination | Someone or something reacts | Your email, chat, ticket, queue… |
Do not try signature + CLI + destination on the same day if this is your first webhook. Understand the event first; then the message or action; then the glue.
Common mistakes
| Symptom | Likely cause | What to check |
|---|---|---|
| No event arrives at all | The address is registered in the other mode | Dashboard → Developers → Webhooks, and confirm you are in test |
| Invalid signature | The secret belongs to another address or the other mode | Copy the secret for that exact address |
| Stripe delivers, but nothing happens | The problem is your destination, not Stripe | Test the destination on its own, without Stripe in the way |
| Four alerts for one unpaid invoice | Stripe retries and repeats are not discarded | Store the evt_… or invoice id you already processed |
Next step
When the event is clear:
- An HTTPS address of yours that receives the delivery, verifies the signature, and keeps only the
invoice.payment_failedevents. - The action — alert the team, tag the customer, enqueue a retry… per your process.
- Run the whole thing in test mode before touching live.
This guide ends at the signed Stripe event. Where the alert goes (email, chat, CRM, queue…) is your choice, based on your process.
Guided automations
Apply what you learned in this guide with one of our automations: