Stripe Webhooks: Invoice.paid vs Checkout.Session.Completed

Viewed 1707

I use Stripes' webhooks and want to get notified, if the customer successfully "paid the bill". I came across two webhooks, which in my opinion both could do the job:

  1. Webhook "invoice.paid" - According to Stripe doc: Occurs whenever an invoice payment attempt succeeds or an invoice is marked as paid out-of-band.
  2. Webhook "checkout.session.completed" - According to Stripe doc: Occurs when a Checkout Session has been successfully completed.

My questions are:

  1. I don't understand the second part of the "invoice.paid" webhook: "invoice is marked as paid out-of-band" -> What does "out-of-band" mean? Is this to be considered a successful payment?
  2. Regarding "checkout.session.complete" -> This can also occur, if payment fails - correct?
  3. Which webhooks shall I consider (or are there other webhooks) to see the status "customer paid the bill successfully"?
  4. What is more, I don't really know if disputes should be considered as successful payments or not: On one hand, I get a invoice.paid webhook, on the other hand, I get a charge.dispute.created webhook. geeezus...

I appreciate your help! Thanks.

2 Answers

I don't understand the second part of the "invoice.paid" webhook: "invoice is marked as paid out-of-band" -> What does "out-of-band" mean? Is this to be considered a successful payment?

This is specifically referring to marking an invoice paid out of band (ie, the customer paid you outside of Stripe and you want to mark the Stripe invoice paid without collecting a payment). This will not involve an actual payment, but does transition the invoice to status=paid so this event fires.

Regarding "checkout.session.complete" -> This can also occur, if payment fails - correct?

This event signals only that the Checkout session is complete. Depending on the mode use for Checkout, this may or may not involve a payment. If an immediate payment is expected, the session will only complete if that payment is successful. For example mode=setup or mode=subscription with a free trial will not involve an immediate payment. A subscription with trial, though, will create a $0 invoice and fire invoice.paid.

Which webhooks shall I consider (or are there other webhooks) to see the status "customer paid the bill successfully"?

This depends on what you mean by "paid" and "bill". If you mean specifically for invoices (whether related to subscriptions or not), then invoice.paid is a good choice. You can then filter for amounts greater than $0 etc to further constrain was "paid" means.

What is more, I don't really know if disputes should be considered as successful payments or not: On one hand, I get a invoice.paid webhook, on the other hand, I get a charge.dispute.created webhook.

Disputes are not payments, and should be an entirely separate discussion. You can only have a dispute after a payment. Suggest starting by reading the docs on disputes.

To summarize: What are you really trying to do? These events are related and sometimes overlap, but not always. It highly depends on what you're doing.

What's going on?

  • When you create a checkout session it will have an id, which you'll store in your database next to the user who started the checkout session.
  • When you receive an invoice.paid webhook event, it does not have any link back to the checkout session! (so you'll know someone paid, but you won't know who paid!)
  • checkout.session.completed solves this because it contains the id of the checkout session and the stripe customer id, which allows you to link the two, so you basically have a mapping from your customer ids to stripe's customer ids.
  • So simply grab the customer id from the checkout.session.completed event and store it in your database next to the relevant user, that way you'll be able to tell which one of your users is paying you when you receive an invoice.paid event!

How can this be implemented?

  1. When a checkout session is started, store the checkout session id next to the user who started the session so you can look it up later
  2. When you see checkout.session.completed, look at the accompanying JSON and take the stripe customer number and store it in your database (e.g. a column like stripe_id in users table). To figure out which of your users it's for, use the checkout session id to look it up in your database (i.e. the data you stored in step 1)
  3. Now that you have the stripe customer id stored in your users table, whenever you see invoice.paid, look at the accompanying JSON, take the stripe customer number, look it up in your users table to find who paid, and update the expiry date of their subscription to 1 month into the future.

That's it!

Also good to know

  • Both checkout.session.completed and invoice.paid events are triggered when someone new subscribes, and only invoice.paid is triggered each month thereafter (presuming the user had enough funds and didn't cancel)

  • You can get to the stripe customer number in both webhook events like so (this is ruby, but should be similar with js or python):

payload = request.body.read
data = JSON.parse(payload, symbolize_names: true)
data.object.customer
=> "cus_Lvyv721cJGpYB1"
Related