• 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

Mental model
  1. Stripe tries to charge
  2. Payment fails
  3. It emits invoice.payment_failed
  4. Your URL receives the JSON
PieceTypical valueWhy it matters
typeinvoice.payment_failedFilter only failed charges
data.objectinvoice objectCustomer, amount, status
id of the eventevt_…Unique identifier: use it so you never process the same notice twice
livemodefalse in testTells you whether the charge was real or simulated

Useful fields on the invoice:

FieldUse
customercus_… id
customer_emailContact (when present)
amount_due + currencyAmount in cents → e.g. 1900 + eur = 19.00 EUR
hosted_invoice_urlLink for the customer to pay
attempt_countHow 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):

Language: json

{
  "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):

  1. Open Stripe and turn on Test mode (the switch sits at the top of the Dashboard).

  2. 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 login once to connect it to your account.

  3. In a terminal:

    Language: bash

    stripe trigger invoice.payment_failed
  4. Dashboard → DevelopersEvents: open invoice.payment_failed and 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:

LayerWhat it provesWhere
Stripe emitterStripe delivers an eventAbove (stripe trigger + Events)
DestinationSomeone or something reactsYour 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

SymptomLikely causeWhat to check
No event arrives at allThe address is registered in the other modeDashboard → Developers → Webhooks, and confirm you are in test
Invalid signatureThe secret belongs to another address or the other modeCopy the secret for that exact address
Stripe delivers, but nothing happensThe problem is your destination, not StripeTest the destination on its own, without Stripe in the way
Four alerts for one unpaid invoiceStripe retries and repeats are not discardedStore the evt_… or invoice id you already processed

Next step

When the event is clear:

  1. An HTTPS address of yours that receives the delivery, verifies the signature, and keeps only the invoice.payment_failed events.
  2. The action — alert the team, tag the customer, enqueue a retry… per your process.
  3. 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: