Verify webhooks in Ruby
⚠️
Preview — this feature is being built; the design below is the spec.
Check the syntic-signature header before trusting a webhook delivery. The header carries a timestamp t and one or more v1 signatures (several during secret rotation). The signature is the hex HMAC-SHA256 of t + "." + raw body, keyed with your endpoint’s signing secret. Reject the delivery if no v1 matches in constant time, or if t is more than 300 seconds away from your clock. Always hash the raw body bytes, before any JSON parsing.
require "openssl"
module SynticWebhooks
def self.verify(header, raw_body, secret, now: Time.now.to_i)
pairs = header.to_s.split(",").map { |p| p.strip.split("=", 2) }.select { |kv| kv.size == 2 }
t = pairs.find { |k, _| k == "t" }&.last
sigs = pairs.select { |k, _| k == "v1" }.map(&:last)
return false if t.nil? || (now - t.to_i).abs > 300
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{raw_body}")
sigs.any? { |s| s.bytesize == expected.bytesize && OpenSSL.fixed_length_secure_compare(s, expected) }
end
end
# Sinatra
post "/syntic" do
raw = request.body.read
halt 400 unless SynticWebhooks.verify(request.env["HTTP_SYNTIC_SIGNATURE"], raw, ENV.fetch("SYNTIC_WEBHOOK_SECRET"))
WebhookJob.perform_later(raw) # process later
200
endIn Rails, read request.raw_post rather than params, which has already been parsed and re-ordered.
Test it
Send a test event to your endpoint, or recompute a signature by hand as shown on Webhooks.
Was this page helpful?