Stripe Checkout webhook - which event to listen for
Asked Answered
A

1

18

Background:

I've implemented Stripe Checkout in a React app, and a webhook that successfully listens to events.

These are the events that the webhook hears after a successful Checkout:

checkout.session.completed
charge.succeeded
payment_method.attached
customer.created
customer.updated
invoice.created
invoice.finalized
customer.subscription.created
invoice.updated
customer.subscription.updated
invoice.paid
invoice.payment_succeeded
payment_intent.succeeded
payment_intent.created

What I'm trying to do:

I need to save a value to the database after a customer successfully creates a subscription. This value helps enable paid features, so I have to be sure the webhook catches all successful events.


Question:

Can I consistently rely on only one of these webhook events to indicate success? If not, which ones are the most important to listen for?

I think the most important events for my use case are probably checkout.session.completed, charge.succeeded, and payment_intent.succeeded, but I'm not too sure on how these are different and if/when some of them may not appear.

The docs indicate:

There are three ways to accept payments on Stripe today:

  • Stripe Checkout
  • Charges API
  • Payment Intents API

and:

  • checkout.session.completed: Occurs when a Checkout Session has been successfully completed.
  • charge.succeeded: Occurs whenever a charge is successful.
  • payment_intent.succeeded: Occurs when a PaymentIntent has successfully completed payment.

So my guess is that each of checkout.session.completed, charge.succeeded, and payment_intent.succeeded indicate success, and the reason all three are returned is that they correspond to the three ways to currently accept payments on Stripe.

If this is the case, can I reliably listen for only checkout.session.completed to indicate success?


Other info:

stripe listen --forward-to localhost:4242/myWebHook output after a successful Checkout session:

(actual evt_ values replaced for simplicity)

2022-05-08   --> checkout.session.completed [evt_123456]
2022-05-08   --> charge.succeeded [evt_123456]
2022-05-08   --> payment_method.attached [evt_123456]
2022-05-08   --> customer.created [evt_123456]
2022-05-08   --> customer.updated [evt_123456]
2022-05-08   --> invoice.created [evt_123456]
2022-05-08   --> invoice.finalized [evt_123456]
2022-05-08   --> customer.subscription.created [evt_123456]
2022-05-08   --> invoice.updated [evt_123456]
2022-05-08   --> customer.subscription.updated [evt_123456]
2022-05-08   --> invoice.paid [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   --> invoice.payment_succeeded [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   <--  [200] POST http://localhost:4242/myWebHook  [evt_123456]
2022-05-08   --> payment_intent.succeeded [evt_123456]
2022-05-08   <--  [200] POST http://localhost:3000/myWebHook  [evt_123456]
2022-05-08   --> payment_intent.created [evt_123456]
2022-05-08   <--  [200] POST http://localhost:3000/myWebHook  [evt_123456]
Aubade answered 8/5, 2022 at 23:18 Comment(1)
Don't forget that you can store metadata in several major Stripe objects that can be picked up later by the webhook handler. The best general approach to a Stripe webhook is to store incoming events in a database with a primary key of event ID and mark new entries as "unprocessed." Then a script runs periodically that handles the unprocessed entries in creation timestamp order. You'll need such a script to correctly process subscription renewals. Note that webhooks are not guaranteed to arrive (e.g. technical problems like an expired TLS certificate) nor arrive in order.Kangaroo
D
25

The events which most users usually listen for successful subscription creation are :

There's no single correct answer because which event you would want to listen for would depend on a variety of factors e.g.

  • if the event you're listening for contains the information you need
  • for checkout.session.completed, if you're only taking in card payments (no async payment methods), then yes, you can rely on checkout.session.completed to save a value to the database after a customer successfully creates a subscription. However, if you do accept async payment methods as well, you would want to listen for and handle additional events listed here : https://stripe.com/docs/payments/checkout/fulfill-orders#delayed-notification
  • Some users want to save all paid invoices (including those for subscription renewals) to their database. In which case, it may make more sense to listen for the invoice.paid event.

There are reasons to not use charge.succeeded or payment_intent.succeeded :

  • If the invoice amount = 0, e.g. trials, there will be no PaymentIntent / Charge created.
  • If you mark the invoice as paid out of band, then the PaymentIntent would be cancelled and you would never receive these two events.

I'd suggest reading through https://stripe.com/docs/billing/subscriptions/overview for a better understanding of how subscriptions work.

Dobbins answered 9/5, 2022 at 5:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.