5 ms·
Our Stripe Billing implementation and the one webhook to rule them all
- tnolet 8y agoOP here. I'm super curious about other folks using Stripe Billing and their experiences. My SaaS is fairly young, so I probably have missed some things.
- kwindla 8y agoGreat article! Here was a related discussion, last week: https://news.ycombinator.com/item?id=19556579 https://news.ycombinator.com/item?id=19556579
- tnolet 8y agoOh wow that's a cool post. The comments kinda echo the spirit of my post. Yes, Stripe has a good API and SDK. The actual code is not the problem. The problem is the whole workflow, how it interacts with your customer and with your business backend processes.
- owenwil 8y agoI missed something in here that seems important: you don't handle VATMOSS? Or are you doing that with Moneybird?
- blackdogie 8y agoThanks for sharing. Very timely for my team.
- DonHopkins 8y agoThe problem I had with implementing subscriptions with Stripe is that the batches of messages from the webhook could come back in clumps in random order. So they would have references to objects that I hadn't been notified about their creation yet. Theoretically and rarely a webhook could fail and be retransmitted arbitrarily later due to bad weather on the internet, so you have to be able to tolerate that, but practically and often it sent bunches of messages all at effectively the same time, which caused them to be processed by my web server in random order. I finally gave up trying to structure the code so it could create objects in any order, and deal with objects it hadn't heard about yet, and just treated the webhook callback as a sign that I should soon make a request back to Stripe and ask them for ALL the events that had transpired. So I'd log all the information in the webhook just for chuckles, then schedule a task that polled Stripe for batches of events, and dealt with them all at once without anything slipping through the cracks because of random reordering in transit.
- tnolet 8y agoHmm, interesting. Have not bumped into that one yet. This might be due to the fact I only use one webhook or that I'm a small time player. The volume of hooks is pretty managable right now.
- DonHopkins 8y agoGoing from straightforward one-time purchases to maintaining subscriptions for an online service is a huge jump in complexity! Then throw in free trials, prorating subscriptions, upgrading monthly to yearly, etc. There are so many issues and edge cases with recurring charges on credit cards that can blindside you.
- tnolet 8y agoFunny you say that, because I have actually had all of those cases occur and my current setup handled that just fine. Guess I'm lucky ;o)
- WestCoastJustin 8y agoI have run into this also using subscriptions + webhooks. For example, I am using the "charge.succeeded" webhook event to send a custom email receipt on my backend. But, for a few new customers there seems to be a race condition where the "charge.succeeded" webhook event will arrive, before the API call in my code returns a success, so there is now an event but I have no idea how to tie this back to a user. I am using the API to create a new customer and updating their user DB record with the customer token from stripe. So, I get into a situation where I do not know what customer to send the email too (because I don't have the customer token yet). I ended up just adding a HTTP 503 error (Service is Unavailable), for this specific webhook event when I cannot find the user, so the Stripe API will retry that event. This is a hack but it works. This just started happening a few weeks ago. There are a few of these things popping up here and there that I need to deal with but generally it works really well.
- DonHopkins 8y ago
- DonHopkins 8y agoOne thing about the Stripe API that I loved was that I could intertwingle it with my own admin interface, by pushing descriptions and metadata properties into stripe objects that had url links back into corresponding objects in my admin interface (like users, products, coupons, subscriptions, transactions, etc). Though it may not be explicitly documented, Stripe's web site is smart enough to make the urls in metadata be clickable links, which is a godsend. So I didn't have to duplicate stuff you could do on stripe's site in my own admin interface, I could just link back and forth between them!
- eemax 8y agoCool post! I don't have a ton of experience using Stripe, but shouldn't you at least be handling some sort of payment_failed webhook? It looks like you call _createSubscription and set the initial value for currentPeriodEnds before you know the payment actually succeeded, and since you don't ever check or listen for failed payments, anyone could get a free month (or year) of Checkly by using a bad card, or if the payment just randomly fails. Maybe this isn't a huge deal in the early days, but you and your customer might not even notice the failed payment for quite a while unless you happen to check your Stripe dashboard!
- tnolet 8y agoThis is a great comment. And it's stupid I left this out of the post, as I made a conscious decision to not deal with that now. I should at some stage. I actually had one failing credit card already, but my customer base in in the 30+ under 100 range, so I easily caught it. Also, it was totally benign from an early customer that just had bodged renewal for their card.
- mike1o1 8y agoGreat post! The number of subscriptions that Stripe sends out is really overwhelming at first, and especially the order they can be received in. I ended up coming to the same conclusion that you did, and only end up listening to the invoice payment succeeded and invoice payment failed webhooks. Doing so greatly simplified the process, rather than listening for customer or subscription updates and changes. Great to read others are taking a similar approach!
- politician 8y ago(Yikes) I feel compelled to remind the folks complaining about webhooks arriving out of order of the fallacies of distributed computing. https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing https://en.wikipedia.org/wiki/Fallacies_of_distributed_compu... For goodness sakes, log webhooks to a queue and rebuild the object graph later by probing their API. I would be absolutely blown away if Stripe recommended that folks attempt to process these inline (simple tutorial-level example code notwithstanding). EDIT: And for your own sanity, assume 50% of the webhooks you expected to arrive didn't. Schedule a periodic task to scrape transactions from their API.
- dbbk 8y ago> EDIT: And for your own sanity, assume 50% of the webhooks you expected to arrive didn't. Schedule a periodic task to scrape transactions from their API. I'm pretty sure the Webhooks keep retrying until they get a success response back from your server.
- quelltext 8y agoThey do that but not forever: https://stripe.com/docs/webhooks/best-practices#retry-logic https://stripe.com/docs/webhooks/best-practices#retry-logic
- quelltext 8y agoYeah, this is specifically pointed out here: https://stripe.com/docs/webhooks/best-practices#acknowledge-events-immediately https://stripe.com/docs/webhooks/best-practices#acknowledge-...
- deepwell 8y agoYou just leaked your customers email addresses by improperly obfuscating them. See https://web.archive.org/web/20190409072021im_/https://blog.checklyhq.com/content/images/2019/04/image-3.png https://web.archive.org/web/20190409072021im_/https://blog.c... With knowledge of the font used (which is very easy to figure out as its the Stripe dashboard) everyone can reconstruct these email addresses. You should now notify these customers as well as your supervisory authority.
- tnolet 8y agoThanks for bringing this to my attention. I will have my legal help check it and take the appropriate steps.
- bschwindHN 8y agoI wrote a Stripe subscription integration a couple years ago in Scala. The product never worked out and it's running in free mode now. For fun, here's the main controller code that processes the webhook events (obviously there is more logic in other files) I had never worked with a payment service before so there may be mistakes, but it worked well for us for the time it was in use :) https://gist.github.com/bschwind/1371a196920981c1dea2b0e4f028e442 https://gist.github.com/bschwind/1371a196920981c1dea2b0e4f02...
- dplgk 8y agoWhy does this one webhook "rule them all"? Because you can scrape by by ignoring Stripe's other webhooks?