← Home

Stripe Webhooks Without System Dependencies

Setting up Stripe webhooks in development usually requires installing Stripe CLI as a system dependency, authenticating against the right Stripe account and running it in a separate process. Alternatively, you might use a forwarding service like ngrok, but that also requires additional setup for new development environments (not to mention you will recieve all events across all developers for the associated Stripe test-mode/sandbox webhook).

But not if you use stripe-cli-ruby! It works by wrapping up the platform-specific Stripe CLI executable, authenticating using your Stripe.api_key, and running the stripe listen command as a puma plugin.

Let’s see how it works.

Stripe CLI as a Puma plugin

Running stripe listen as a separate process will force you to depend on something like foreman, unless we run it together with Puma as a plugin:

# lib/puma/plugin/stripe.rb
Puma::Plugin.create do
  def start(launcher)
    launcher.events.on_booted do
      fork do
        exec "stripe listen --api-key #{Stripe.api_key} --forward-to http://localhost:3000/stripe_event"
      end
    end
  end
end

# config/puma.rb
plugin :stripe if ENV["RAILS_ENV"] == "development"

While we’re at it, let’s grab the signing key so we can run the same verification routine we do in production:

secret = `stripe listen --api-key "#{Stripe.api_key}" --print-secret`.chomp

This is how our webhook handler might look:

class StripeEventsController < ActionController::API
  before_action :set_event

  def create
    case event.type
    when 'payment_intent.succeeded'
      payment_intent = event.data.object
      # ...
    end

    head :ok
  end

  private
    def event
      @event ||= Stripe::Webhook.construct_event(
        request.body.read,
        request.headers["stripe-signature"],
        `stripe listen --api-key "#{Stripe.api_key}" --print-secret`.chomp
      )
    rescue => error
      logger.error error
      head :bad_request
    end
end

Stripe CLI as a Ruby gem

So far so good, however, we still depend on developers installing Stripe CLI on their system. What if we could install it as a regular ruby gem? That’s exactly what I’ve done in stripe-cli-ruby, which includes the puma plugin for good measure.

With that, bin/setup will download, authenticate and run Stripe CLI as part of your development server.