Skip to main content

Using Webhooks with Stripe Discussion

General • Asked by Chris Oliver
00d9546ea4a2e25aff2f5cedcad21d81
Fritz Rodriguez Jr.

Hey Chris,

Another great episode!

How would you handle a monthly recurring cost without having the user create an account? For example, a donation site?

Ce795239ba5dd2384fc2f88ffaff5451

If you're doing recurring donations, you'll need to store a User model of some sort so they can come back in and cancel it. You may not need passwords in that case, a secret token that you log them in with via email would work. You could make it work similar to a password reset token basically so they could manage their monthly donation via email.

00d9546ea4a2e25aff2f5cedcad21d81
Fritz Rodriguez Jr.

Thanks for pointing me in the right direction...appreciate it. Happy Sunday!!:)


926d3d30e19f776302d1763e79b4b28d

Great episode! Thanks!

Could you recommend some article or gem that could handle some kind of "leaky bucket" algorithm?

I wanna build an app where many different Users will be able to send API Calls to a third party website which would return a link to generated by this site document in pdf.

I am wondering if I should construct an algorithm which would queue up all these requests so they do not fire off at exact same time, or is it something that Rails4 handles? Or maybe its not a problem at all?

Another thing I would like to set up is a queue for request so they would fire for example not more often than f.e. 2 calls per second. And on the other hand, if one call returns f.e. 429 error, an app would try again in f.e. 30 seconds and the other ones which would be in a queue (triggered by another Users) would be put on hold until the errored one would be successful after another or several attempts...

Is there any gem that handles that kind of requests scheduling for API calls?

Would you build a seperate model for API calls and queue them up in the database with timestamps?.. If so, then what is the best way to schedule a controller to reach to this database of queued requests and make an api call in the background?

f.e. Shopify handles that kind of errors on their server site,.. but I'd like to be able to control it from my app as not all of the services handle it... https://docs.shopify.com/ap...

Thanks for great tutorials!!! :)

Ce795239ba5dd2384fc2f88ffaff5451

You would probably want to do this on the Rack level. What you're basically attempting is a rate limiting / throttling algorithm for the API. This is best done in Rack because it will be faster than going all the way through the Rails stack. Something like this is a good starting point. https://github.com/bendiken...

I'm not sure that it supports the leaky bucket algorithm that shopify does, but it's a good starting point.

926d3d30e19f776302d1763e79b4b28d

Thanks a lot for help! :)


0c127701cbc6b744a88618a05b7c2c06

Hi Chris!

Another great episode about something i was just gonna start working with! I have a question about webhooks from Stripe. Is it possible to use webhooks to get update of the status of the subscription? If it's active or not, so that if someone isn't paying our a charge fails that it will update the user's account.

Again thx for a great episode!

Ce795239ba5dd2384fc2f88ffaff5451

Yep! That's exactly right. You can get notifications of all the subscription events and keep that in sync and send out any emails you need to when they get canceled.


596d5d07cd498875dab9fbe784770802

Awesome episode, Chris. I just loved it. I'm trying to build a simple website for user to listen musics online with subscription model. The idea is simple, users pay monthly subscription and every month, they can listen any new 20 songs. Everything works well as Stripe webhooks fired every month.

The problem is when someone pay YEARLY subscription (20% discounted). How can I deal the same logic as now, Stripe webhook only fire in yearly basis. Would love to hear your thoughts, Chris!

Ce795239ba5dd2384fc2f88ffaff5451

Yeah, that gets pretty tricky. We had to do that at One Month and it became kind of a complicated piece of work. It was a nightly cron job that calculated the end of the month on the same schedule that Stripe would do monthly. We would look up users expiring and give them access to their next course. You also have to take into account things like extra days in months, leap years, etc. It's really not fun so if you can get by without offering the yearly plan for something like this, I'd probably recommend it.

You could also have a simple 30 day schedule for every yearly user that's a little different than the monthly users. People generally won't notice the difference and it's a lot simpler than making a perfect copy of what Stripe does yourself.

596d5d07cd498875dab9fbe784770802

Thanks a lot, Chris! And I totally agreed with your suggestions. Kudos to you!

3c58c3aadbf5de245b21f8d11981642a

Maybe I'm missing something, but couldn't you create a subscription model that stores an expiration date populated with the current_period_end attribute from the Stripe subscription object?
I also wonder which webhook is the better choice for subscriptions -- charge.succeeded or invoice.payment_succeeded.

Ce795239ba5dd2384fc2f88ffaff5451

Unfortunately, the current_period_end will be the end of the year, not the end of every month on a yearly plan. You won't get that attribute updated each month to enable your users to have a subscription that acts monthly but is actually yearly.

I think both of those webhooks will fire at the same time for subscriptions. The invoice is generally better because it's tied to the subscription, but the charge attributes might also link to it.

3efbfd2351447c3aa27a31aa0978683d

Okay, so I know this thread is a few months old, but in case someone revisits like I have, I had a thought...

What if you had an additional subscription plan in Stripe that customers never see that pairs with the annual subscription plan but has a monthly interval and a monthly amount equal to the annual discounted rate divided by twelve? The customer would sign up for the annual plan at the discounted rate and be charged once a year. The "charge.succeeded" event from that charge could trigger the creation/renewal of a subscription to this hidden monthly plan for the same customer with a credit balance for the annual amount paid so that each month it would renew automatically until the balance ran out, which is equal to the duration of the annual subscription. The annual plan would handle the billing, but you could use the webhooks from the monthly plan to trigger the monthly logic in your app.

You could also make this hidden plan free, but it seems the credit balance would automatically suspend services without extra logic to check if the annual subscription had been renewed.

Just a thought, but not one outrageously thought out. :-)

EDIT: With a little more thought, perhaps it is better to make the hidden plan free so as not to affect the values of Stripe balances associated with the entire application (as opposed to the user).

Cheers,
Tim

D2f6063450980a9fe65537acac566628

Hi, can you make a video of this for Braintree subscription because it's lack of documentation and demo for Braintree rails app.


1f8800572c0fcc4072029965ced06172

Hey Chris, quick question...I followed your tutorial for setting up webhooks with stripe using the stripe.rb config file. How would you retrieve the line items for an invoice using the latest stipe API? I have the parent elements retrieval working fine. But the line items are an issue. Here is the code i am using, it's returning nil. CODE: invoice = event.data.object, invoice.lines.data[0].plan AND invoice.lines.data[0].plan.id returns => id method error. Any ideas?

Ce795239ba5dd2384fc2f88ffaff5451

Have you printed out the result from this line? invoice.lines.data[0].plan

I'm wondering if it isn't returning a hash, but some other type (like maybe the plan is just the string ID instead and it doesn't auto-include the nested plan attributes). I don't know off the top of my head, but I'd recommend checking that out and seeing what you get.


Da86d46df7b85ed580627dcfd91cc512

Chris, since I'm running Koudoku the setup is a bit different and I'm running into an issue. I posted a question on SO: http://stackoverflow.com/qu... Any suggestion you could provide would be hugely helpful!

Thanks!

Ce795239ba5dd2384fc2f88ffaff5451

I think Matt's answer is correct. Basically just need to reverse your way back to the code that creates the Subscription object (in your database) and attaches that to the user. Maybe it's not saving?

Da86d46df7b85ed580627dcfd91cc512

Chris, thanks for your quick response. Yep. I was using a Stripe event that corresponded to a subscription that no longer existed. I had reset the database since I last created the user I was attempting this with. After creating a new user with a new subscription, the event was successfully saved into the database. Thanks!

Ce795239ba5dd2384fc2f88ffaff5451

Awesome! Also if things get out of sync for some reason and you've got the customer ID, you can generally hit their API and ask for the customer's subscriptions and sync them to your database. That way you won't have to clear your DB or anything. It's that keeping-in-sync process that really would be nice if it was easily automated.


5879c9b86aee1ec41a4b0d19f482812b

Hey @excid3:disqus when I'm at minute 21 ish where you call event.data.object I don't see "source". I believe I have a bug but am not getting any errors. Aditionally, the object is saving all of the data as nill (EX: card_brand: nil, card_exp_month: nil).

Here's a screen shot of what I get when I call event.data.object and another screen shot of what happens when I call RecordCharges.new.call(event).

Any suggestions?

3efbfd2351447c3aa27a31aa0978683d

Hi @Alex Kehaya. I don't know if you've already solved your problem, but it appears you are grabbing the wrong event. You are grabbing a subscribe event not a charge event, indicated by the fact that the event.data.object response says "Stripe::Subscription" and the id starts with "sub_". A charge event id starts with "ch_". Go back to "Events & webhooks" in your Stripe dashboard and find an event that has the phrase "was charged". That should be a charge event. When you click on it, in the "Event Data" section the id should start with "ch_". Use the event id that appears under "Event Details" at the top.
And don't worry, I did the same thing. :-)
Cheers,
Tim

5879c9b86aee1ec41a4b0d19f482812b

Thanks yeah I did figure it out and that was my issue!! Thanks:)


586363b3f73bf75271280465eb5fb312

I'm using `customer.subscription.updated` event to get notification when a recurring charge occurs, so that I can update user table for relevant attribute.

But this event will also trigger when customer manually changes/renew their plan from admin-panel before billing period ends; in this case too I'll update user table fields (similarly as above) but here, to update these attributes I've defined the code separately in my subscriptions controller.

There will be duplication of code and app will try to update user table fields, first based on controller code and then based on webhook event code. How to avoid this?


Ace901b2acf34bc95dd18f9846bd1a80

One issue with the current set up that I noticed was that if you delete the subscription on the Stripe dashboard -- it is still displayed on the site end. I believe this is caused from not having a webhook? Any advice on how I could build this feature in so they stay in sync.

Ce795239ba5dd2384fc2f88ffaff5451

Yep. You can implement the subscription webhook that gets triggered on delete (or cancel, don't remember the exact name) and just have it remove it from the user on your end. You'd build it same as the other one, but just remove the subscription ID from the user.

Ace901b2acf34bc95dd18f9846bd1a80

Thanks,

Here's the code I added to the stripe.rb file in case anyone is trying it out.

Ce795239ba5dd2384fc2f88ffaff5451

Perfect. Thanks for sharing that Bob! :)


6eaccbfd2c2dad77c0f524c824f026d1

Hey Chris, awesome episode. Quick question though, how would you handle error handling for webhooks? You kinda glossed over it for a minute but am wondering on making an app production ready, would you suggest just swallowing the error or it doesn't matter because of precautions like database constraints?

Ce795239ba5dd2384fc2f88ffaff5451

It typically depends on what you're doing with the webhooks and what the potential errors are. Maybe a user deleted their account and so you no longer have a matching record for the stripe customer ID, in that case you might let the webhook silently fail. In other cases, you probably want to send yourself an alert email if something goes wrong that's out of the ordinary so you can fix it. I usually leave that for exception_notification or a tool like Airbrake/AppSignal/Sentry to log and handle sending the notification for me.

In general you never want webhooks to fail, so either send yourself an alert so you can fix that case when something went wrong or build in the logic to allow certain cases to skip gracefully and you should be fine.


E1be3b3db2cfae81b89ac2661fbf3d5c

A great (free) service to tunnel your localhost is ngrok https://ngrok.com/download :
- download
- unzip
- put in `/usr/local/bin`
- start via `ngrok http 3000` (after you started `rails s`)
- Check output to see where website is available


Fbeed7a16534b60e95d2a835d9e3c310

Hi Chris,

Awesome episode, helped me add Stripe subscription with minimal work. Thanks!

Do you think we will see a follow up for webhooks, I am trying to catch failed charges and send the users to update the card. Similar to what you have in gorails.com. Any help would be appreciated.

Ce795239ba5dd2384fc2f88ffaff5451

You can just listen to the charge.failed event and send an email accordingly if you want. Even better, you can use a service like Profitwell to handle the failed payments (and soon to expire cards) for you and let them optimize the email text and things. That's what I use and it's generally better at recovering failed payments than your own stuff because they can learn and improve over a vast set of businesses rather than just your single one.

Fbeed7a16534b60e95d2a835d9e3c310

Thanks Chirs, Profitwell looks like the way to go, given the numerous tools they provide out of the box. Will give it a try, also are you using their RETAIN Plan to handle churn.


7bf763eba9e42a7c66cef85b760ed726

Awesome! But how do you implement the extra "account" attribute for managed accounts as seen here: https://stripe.com/docs/con...


Ceb7c310b1ecb139d12a9ebca31a6830

Thanks so much for putting this series together. Very helpful. I wanted to ask about the stripe_id and being unique in the chargers model. As I follow, from the previous episodes, the stripe_id is the 'Stripe Customer Id' Every user(subscribed) has only 1 stripe_id. In this episode you set users has_many charges and charge belongs_to a user. By making strip_id unique and not allowing duplicates, won't it fail to store all the charges for the user? So if its a monthly subscription, after 6 months, a user should have 6 charge records on the table with each record having the same user_id and stripe_id? Sorry I am missing something. Still new to me. ;)

Ce795239ba5dd2384fc2f88ffaff5451

Actually, stripe_id on the User model references a Stripe::Customer's ID and stripe_id on a Charge references a Stripe::Charge's ID. That's a great point to clarify!

We're just storing references to the different objects in Stripe so we can load up those objects whether they're a Customer, a Charge, or a Subscription, etc.


457bb2d2973f4961679f3268899fd545
I am trying to implement this episode (exactly as shown) in my test app using Rails 5.2 on Cloud 9 with no luck at all.  The Stripe parameters (charge details) arrive safely into our app but then rejected because of "Can't verify CSRF token authenticity" error as shown in the console.

I have tried all the solutions found on the web including adding:

- skip_before_action :verify_authenticity_token
- protect_from_forgery with: :null_session
- skip_before_action :verify_authenticity_token, only: [:webhook]

to stripe_controller 

and I also tried adding 
- protect_from_forgery unless: -> { request.format.json? } 

to application controller.

Please can someone help as the episode seemed to be great but is not working for me.

2d281ba7fe536b38eef189a689513e6a
Hey Chris. I'm running into some interesting issues with having the StripeEvent.configure code in config/initializers/stripe.rb - mainly, I have to restart my server if I change any service object code - And I'm getting a  
ArgumentError (A copy of StripeEvents::InvoicePaymentFailed has been removed from the module tree but is still active!) if I hit the webhook a 2nd time. This may be due to the fact that I'm calling a service object in a separate file.

I'm documenting my findings here: https://github.com/integrallis/stripe_event/issues/108 - I'll report back once I sort it out.

If you have any ideas or comments in the mean time, I'm all ears!

8d7f5ba6d799cf1df55a33fa9f35cb9f
What is required to add a signature header and endpoint secret? Stripe now requires them as per their v2.0.0 to verify requests with a StripeEvent.signing_secret
Ce795239ba5dd2384fc2f88ffaff5451
All you do is grab the signing secret from your Stripe account and set that variable like you would do with the other keys. Link to how to use it is here: https://github.com/integrallis/stripe_event#authenticating-webhooks-with-signatures

I'll do a quick episode on this shortly!
8d7f5ba6d799cf1df55a33fa9f35cb9f

Sorry Chris I forgot to write back to you! I got it working at like 3:30AM lol Stripe docs made it seem that you needed the event payload, the Stripe-Signature header, and the endpoint’s secret to perform the verification, which was what I was writing here about.

I was able to simply add the signing_secret to config/secrets.yml, and in config/initializers/stripe.rb. My first deploy to heroku only added the signing secret to stripe.rb and was throwing an issue.

config/initializers/stripe.rb
 Rails.configuration.stripe = {
:stripe_publishable_key => ENV['STRIPE_PUBLISHABLE_KEY'],
:stripe_secret_key => ENV['STRIPE_SECRET_KEY']
:stripe_secret_key => ENV['STRIPE_SECRET_KEY'],
:stripe_signing_secret => ENV['STRIPE_SIGNING_SECRET']
}

class RecordCharges
def call(event)
....


97598e2e5acd70619c1e7a24af523a64

Hey Chris,

I'm trying to figure out how to fix up my code and move the RecordCharges class out of my initializer and into a PlainOldRubyObject™ located in app/services/webhook_handlers/stripe_webhook_handler.rb but am running into an issue that because the StripeEvent is called in an initializer (config/initializers/stripe.rb), the rest of the app isn't loaded so it is not able to call the class. Any chance you could help me out?

Thanks,

Rich

Ce795239ba5dd2384fc2f88ffaff5451

It should automatically load that file as long as you named it correctly. class WebhookHandlers::StripeWebhookHandler; end

97598e2e5acd70619c1e7a24af523a64

Thanks for getting back to me! I am still struggling with this :/

Edit: Got it working but it feels dirty...

Here's what I have:

/config/intializers/stripe.rb

...
StripeEvent.configure do |events|
  events.subscribe 'charge.succeeded', WebhookHandlers::StripeWebhookHandler.new
end

/app/services/webhook_handlers/stripe_webhook_handler.rb

module WebhookHandlers
  module StripeWebhookHandler
    class RecordCharges
      def call(event)
        ...
      end
    end
  end
end

Any chance you could point me in the right direction on wheather or not this is clean?


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 18,000+ developers who get early access to new screencasts, articles, guides, updates, and more.

By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

More of a social being? We're also on Twitter and YouTube.