Handling Stripe Webhooks in Rails

Handling Stripe Webhooks in Rails

If you're using Stripe in your Rails application to process payments or manage subscriptions, chances are you'll need to prepare your application to handle webhooks. Stripe uses webhooks (HTTP POST requests) to notify applications about events that happen in an account. For example, if your application offers a subscription service, you'll want to know when subscriptions are renewed or when renewal payments fail.

In this post, I'll run through the process of preparing a Rails application to receive Stripe webhooks. I'll start by configuring Stripe to send webhooks, then add the route and controller to process requests, and end by testing the webhook endpoint with requests from Stripe.

I'll assume that your application is already connected to a Stripe account via the stripe gem.

Configuring Stripe

Each Stripe webhook is associated with a single event that occurs in an account, and includes the id to that event in the request body. A complete list of events can be found in the event documentation.

Stripe can be configured to send webhooks in multiple ways. You can specify a single URL to receive all events or you can specify a URL for select events by event type. I prefer to specify a URL for each event type to avoid unnecessary requests to my application and to avoid conditional logic in my code.

For this post, I'll set up my application to only receive webhooks for charge.succeeded events.

Stripe Webhook Settings

Adding the Route

Since I configured my Stripe account to send webhooks to /stripe/charges, I'll need to add that route to my Rails application. I prefer to organize Stripe webhook routes under a stripe namespace.

# config/routes.rb
namespace :stripe do
  resources :charges, only: :create
end

This gives me the following endpoint:

POST  /stripe/charges(.:format)  stripe/charges#create

Adding the Controller

According to the Stripe webhook documentation, the POST request body will include an Event record ID. Since this endpoint will be open to anyone, Stripe suggests following this process to ensure that you're responding to a request from them:

  1. Grab the Event ID from the request body.
  2. Use the Event ID to fetch the Event object via the Stripe API.
  3. Take action on the Event object.

Stripe will consider a response with a 2XX HTTP status to be successful. Stripe will consider anything else to have failed and will retry the request each hour for up to 3 days as long as a failed response is returned.

It will be necessary to skip the default Rails authenticity token check since this endpoint will respond to requests from Stripe. Here is the controller.

# app/controllers/stripe/charges_controller.rb
class Stripe::ChargesController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :create

  def create
    event = Stripe::Event.retrieve(params[:id])
    # do something with charge success event
    render nothing: true, status: 201
  rescue Stripe::APIConnectionError, Stripe::StripeError
    render nothing: true, status: 400
  end
end

Processing the Event

In this example application, when a charge is successfully processed, I'll want to update the local charge record's processed_at timestamp and send the user a confirmation email. I'll encapsulate that process in a service.

# app/services/charge_success.rb
class ChargeSuccess
  def initialize(event: event)
    @stripe_charge = event.data.object
  end

  def process
    charge.update_column(processed_at: Time.zone.now)
    ChargeMailer.confirmation(charge).deliver_later
  end

  private

  def charge
    @charge ||= Charge.where(stripe_charge_id: @stripe_charge.id).first!
  end
end

With this addition, I'll go back to the controller and add the ChargeSuccess service.

# app/controllers/stripe/events_controller.rb
  def create
    event = Stripe::Event.retrieve(params[:id])
    ChargeSuccess.new(event: event).process
    # ...
  end

Local Testing

At this point the webhook endpoint is complete. However, I like to test the endpoint with an actual Stripe request before sending it to production.

To test the endpoint with Stripe and my local development server, I'll use ngrok. Ngrok is free, and will create a secure public URL to my local development server.

1). Download and unzip ngrok

$ unzip /path/to/ngrok.zip

2). Run ngrok

$ /path/to/ngrok http 3000

3). Add the ngrok URL to Stripe via the Webhook Settings pane. This will be the same process as shown in the first section, but this time the mode should be set to "test".

http://ca5dc837.ngrok.io/stripe/charges

With the Stripe test webhook URL in place, you'll be able to send requests from Stripe to your webhook endpoint!

Categories: Software Development | Tags: Ruby, Rails

Portrait photo for Jeffrey Jurgajtis Jeffrey Jurgajtis

Jeffrey Jurgajtis enjoys solving business problems with software and developing web applications using Ruby on Rails and Ember.js

Comments


LET US HELP YOU!

We provide a free consultation to discover competitive advantages for your business. Contact us today to schedule an appointment.