Refgrow
Free Developer Reference

Stripe Webhook Events for SaaS

The events a subscription business actually needs, what each one means, what to do when it arrives, and what the payloads look like.

Checkout events

Sent when a customer finishes, or abandons, a Stripe Checkout session.

EventSent whenWhat to do
checkout.session.completedThe customer completed checkout. For subscriptions the first invoice is already paid when the card succeeded.Link the Stripe customer and subscription to your user, and record which referral or campaign the checkout carried.
checkout.session.async_payment_succeededA delayed payment method (for example a bank debit) used at checkout has now succeeded.Fulfil orders that were waiting for the money.
checkout.session.async_payment_failedThat delayed payment failed.Tell the customer and cancel the pending order.
checkout.session.expiredThe session expired without a payment.Optional: send an abandoned-checkout reminder.

Subscriptions events

Most subscription activity is asynchronous, so these are the events that keep your copy of a subscription in sync.

EventSent whenWhat to do
customer.subscription.createdA subscription was created. Its status may be incomplete if the first payment still needs authentication.Store the subscription; do not grant access on this event alone.
customer.subscription.updatedA subscription started or changed: renewal, plan change, coupon, status change, cancellation scheduled.Sync status, plan and cancel_at_period_end. data.previous_attributes shows what changed.
customer.subscription.deletedThe subscription ended.Revoke access.
customer.subscription.trial_will_endThree days before a trial ends (immediately if the trial is shorter).Check there is a payment method and remind the customer.
customer.subscription.pausedThe status changed to paused, for example a trial ended without a payment method.Limit access until the subscription resumes.
customer.subscription.resumedA paused subscription was resumed.Restore access.

Invoices events

Every subscription payment, the first and each renewal, goes through an invoice.

EventSent whenWhat to do
invoice.paidThe invoice was paid.Provision access or extend the access period, after confirming the subscription status is active.
invoice.payment_failedA payment for an invoice failed.Notify the customer and ask for a new payment method; consider Smart Retries.
invoice.payment_action_requiredThe payment needs customer authentication (3D Secure).Send the customer back to complete it.
invoice.upcomingA few days before a renewal, as set in your Billing settings.Add extra invoice items or warn the customer of the charge.
invoice.createdAn invoice was created for a new or renewing subscription.Respond quickly: if Stripe doesn't get a success response, automatic finalization waits up to 72 hours.
invoice.finalization_failedThe invoice could not be finalized, so no payment can be collected.Read last_finalization_error; with Stripe Tax, check automatic_tax.status.

Refunds, disputes and fraud events

Easy to forget, and the reason a refunded or disputed customer can keep their access.

EventSent whenWhat to do
charge.refundedA charge was refunded, fully or partly.Reverse what the payment granted: access, credits, an affiliate commission.
charge.dispute.createdThe customer's bank opened a dispute.Decide whether to submit evidence and whether to restrict the account.
radar.early_fraud_warning.createdThe card issuer flagged the charge as potentially fraudulent, usually before a dispute.Consider refunding proactively to avoid the dispute fee.
payment_intent.succeededA PaymentIntent completed. Fires for one-time payments outside Checkout too.Fulfil one-time purchases made with your own payment form.

Which event should grant access?

Use invoice.paid for subscriptions: it fires for the first payment and every renewal. Before extending access, retrieve the subscription and confirm its status is active, because a paid invoice does not always move a subscription to active. Revoke access on customer.subscription.deleted, and when the status becomes canceled or unpaid.

checkout.session.completed is the right moment to connect the Stripe customer to your user, but not on its own a reliable access signal for renewals.

Webhooks can arrive more than once and out of order. Store the event IDs you have processed and skip repeats, and return a 2xx quickly before doing slow work.

Example payloads

Shortened to the fields most integrations read; real events carry many more. Shapes follow API version 2025-03-31.basil and later, where an invoice’s subscription moved from invoice.subscription to invoice.parent.subscription_details.subscription and billing periods moved to subscription items.

checkout.session.completed

json
{
  "id": "evt_1Q...",
  "object": "event",
  "type": "checkout.session.completed",
  "livemode": false,
  "data": {
    "object": {
      "id": "cs_test_a1...",
      "object": "checkout.session",
      "mode": "subscription",
      "status": "complete",
      "payment_status": "paid",
      "amount_total": 4900,
      "currency": "usd",
      "customer": "cus_...",
      "customer_details": { "email": "jenny@example.com" },
      "subscription": "sub_...",
      "client_reference_id": "user_123",
      "metadata": { "referral_code": "PARTNER42" }
    }
  }
}

invoice.paid (a renewal)

json
{
  "type": "invoice.paid",
  "data": {
    "object": {
      "id": "in_...",
      "object": "invoice",
      "status": "paid",
      "billing_reason": "subscription_cycle",
      "amount_paid": 4900,
      "currency": "usd",
      "customer": "cus_...",
      "customer_email": "jenny@example.com",
      "parent": {
        "type": "subscription_details",
        "subscription_details": {
          "subscription": "sub_...",
          "metadata": { "referral_code": "PARTNER42" }
        }
      }
    }
  }
}

customer.subscription.updated (cancellation scheduled)

json
{
  "type": "customer.subscription.updated",
  "data": {
    "object": {
      "id": "sub_...",
      "object": "subscription",
      "status": "active",
      "customer": "cus_...",
      "cancel_at_period_end": true,
      "items": {
        "data": [
          {
            "price": { "id": "price_...", "recurring": { "interval": "month" } },
            "current_period_end": 1769904000
          }
        ]
      }
    },
    "previous_attributes": { "cancel_at_period_end": false }
  }
}

Testing these events

Forward events to your machine with stripe listen --forward-to localhost:3000/webhook and fire one with stripe trigger checkout.session.completed. For real flows, pay with a Stripe test card, and reach renewals and trial ends with a test clock. If verification fails, paste the request into the signature checker.

Built for Stripe

These are the events Refgrow listens to for you.

Refgrow reads checkout.session.completed, invoice.paid, subscription and refund events from your Stripe account, credits the affiliate behind each payment, and reverses commissions on refunds. No webhook code on your side.

  • Webhook endpoint created for you on connect
  • Recurring commissions on every paid renewal
  • Refunds reverse the commission automatically
  • Dry-run test mode before launch
Start free trialSee pricing

7-day trial · Cancel anytime

Start Free Trial
Stripe Webhook Events for SaaS: List & Examples | Refgrow