DEV Community

Handling stripe webhooks with Ruby on Rails

Maxence Henneron on March 26, 2019

When accepting payments on a Ruby on Rails app, if you want to be aware of every actions that happen on stripe, you will have to implement the stri...
Collapse
 
superails profile image
Yaroslav Shmarov

Receiving Stripe Webhooks 101

Webhooks = incoming POST requests from external services.

To receive incoming Stripe webhooks you do not need any gem.

First configure dashboard.stripe.com to receive wehooks (as described above)

Next, You need to add a route in routes.rb

   resources :webhooks, only: [:create]
Enter fullscreen mode Exit fullscreen mode

add a webhooks controller app/controllers/webhooks_controller.rb

class WebhooksController < ApplicationController
  skip_before_action :authenticate_user!
  skip_before_action :verify_authenticity_token

  def create
    payload = request.body.read
    sig_header = request.env['HTTP_STRIPE_SIGNATURE']
    event = nil

    begin
      event = Stripe::Webhook.construct_event(
        payload, sig_header, Rails.application.credentials.dig(:stripe, :webhook)
      )
    rescue JSON::ParserError => e
      status 400
      return
    rescue Stripe::SignatureVerificationError => e
      # Invalid signature
      puts "Signature error"
      p e
      return
    end

    # Handle the event
    case event.type
    when 'checkout.session.completed'
      session = event.data.object
      @user = User.find_by(stripe_customer_id: session.customer)
      @user.update(subscription_status: 'active')
    when 'customer.subscription.updated', 'customer.subscription.deleted'
      subscription = event.data.object
      @user = User.find_by(stripe_customer_id: subscription.customer)
      @user.update(
        subscription_status: subscription.status,
        plan: subscription.items.data[0].price.lookup_key,
      )
    end

    render json: { message: 'success' }
  end
end
Enter fullscreen mode Exit fullscreen mode

now you can receive webhooks when a subscription was updated or deleted, or when a checkout was completed.

git source

video source

Have a nice day!

Collapse
 
nklbigone profile image
nkl_alexis • Edited

thank you very much for your hard work

I am testing webhook locally I can receive 200 status on post request by my user table never being update I receive 500 status though

if I find user from table I can have him mean all response I can log them on when it comes to update table that's where the problem is

I am using this method you used

though I am running my app from docker is that could be the reason?

Collapse
 
tolgap profile image
Tolga Paksoy

This implementation will not work when your application is not eager loading (like in dev mode).

Your initializers are eager loaded. Anything in your "app" directory is auto loaded.

So you will most likely see errors in your webhook handlers that Stripe::InvoiceEventHandler cannot be reinitialized and is already in the constants tree.

You must move all event handlers to /lib if you want this. The issue then, is autoloading changes to these event handlers in dev mode.

Collapse
 
madeindjs profile image
Alexandre Rousseau

I did exactly the same thing without the StripeEvent gem. Here my post about my integration: rousseau-alexandre.fr/en/tutorial/...

Collapse
 
maxencehenneron profile image
Maxence Henneron

Thank you for sharing this. However, your implementation seem to lack signature verification, which is a security flow. That's the reason I suggested using the StripeEvent gem instead of implementing it manually :)

Collapse
 
merlin2049er profile image
Joe Guerra

can't seem to get a response from my app... i'm trying to trap the checkout_session_completed & charge_failed events from Stripe...
this is in my event handler... in addition to what's above

def handle_checkout_session_completed(event)
# your code goes here
render json: {message: 'Ok, great.'}
end

def handle_charge_failed(event)
# your code goes here
render json: {message: 'Not so good.'}
end

Collapse
 
andrewbrown profile image
Andrew Brown 🇨🇦

Sometimes I find my stripe webhooks fail so I have a cronjob setup to pull stripe once a day to to keep my Rails app in-sync with Stripe.

Collapse
 
happy199 profile image
Happy

Hello. I have a problems about rails stripe webhook. How can i configure stripe if i want to use many webhook endpoint with different signing_key.?

my config/initializers/stripe.rb is this :

StripeEvent.signing_secrets = [
Rails.application.secrets.stripe_ressource_payement_signing_secret,
Rails.application.secrets.stripe_course_payement_signing_secret,
]

and in my config/secret.yml i have :

development:
stripe_secret_key : 'sk_test_............'
stripe_publishable_key : 'pk_test_..........'
stripe_ressource_payement_signing_secret : 'whsec_.........'
stripe_course_payement_signing_secret : 'whsec_........'

But i don't know how to specify in my controller what endpoint to use

Collapse
 
birthdaycorp profile image
birthdaycorp

Thanks so much for posting this! You made it much easier to understand. One question though: if I want to perform an action on a record in my db when the webhook receives an "invoice_payment.succeeded" event, would I write that code in its handler? Presumably I'd pull the needed info from event.data.object?

Collapse
 
gmolini profile image
Guillermo Moliní

I get a undefined method `render' for #CustomerEventHandler:0x00000003ade7a8
whenever I try to handle an error. why are you able to render json?

Collapse
 
maxencehenneron profile image
Maxence Henneron • Edited

The EventHandler is a pure ruby object, and therefore does not have any of the rails helpers. It is actually an error in my article.

If you want to raise an error when the webhook fails, you can use

rescue JSON::ParserError => e
   handle_error()
   raise  # re-raise the exception.
end

The response sent by your server will be a 500 and the webhook will fail

Collapse
 
stenpittet profile image
Sten

Well done, that's a piece that has been missing in our integration so thanks for sharing this.