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
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
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
# 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:
- Grab the
EventID from the request body.
- Use the
EventID to fetch the
Eventobject via the Stripe API.
- Take action on the
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
# app/controllers/stripe/events_controller.rb def create event = Stripe::Event.retrieve(params[:id]) ChargeSuccess.new(event: event).process # ... end
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".
With the Stripe test webhook URL in place, you'll be able to send requests from Stripe to your webhook endpoint!