ruby 34 lines · 7 steps

Verifying webhook signatures with Rails routing constraints

A routing constraint validates an HMAC signature before a webhook request ever reaches its controller.

Explained by highlit
1class WebhookSignatureConstraint
2 def initialize(provider)
3 @provider = provider
4 end
5 
6 def matches?(request)
7 signature = request.headers["X-Webhook-Signature"].to_s
8 return false if signature.blank?
9 
10 payload = request.raw_post
11 expected = OpenSSL::HMAC.hexdigest(
12 "SHA256",
13 Rails.application.credentials.dig(:webhooks, @provider, :secret),
14 payload
15 )
16 
17 ActiveSupport::SecurityUtils.secure_compare("sha256=#{expected}", signature)
18 end
19end
20 
21Rails.application.routes.draw do
22 namespace :webhooks do
23 constraints(WebhookSignatureConstraint.new(:stripe)) do
24 post "stripe", to: "stripe#create"
25 end
26 
27 constraints(WebhookSignatureConstraint.new(:github)) do
28 post "github", to: "github#create"
29 end
30 
31 post "stripe", to: "rejected#unauthorized"
32 post "github", to: "rejected#unauthorized"
33 end
34end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Routing constraints can gate requests on request content, not just path patterns.
  2. 2Always compare signatures with a constant-time function to defeat timing attacks.
  3. 3Ordering routes lets a fallback catch requests that fail the constraint.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Verifying webhook signatures with Rails routing constraints — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code