Vincent is Coding
March 24th, 2024

Easily check webhook signatures

Ruby on Rails

One thing I had to do with all my projects that accept webhook events from my payment provider is to make sure that the incoming request is indeed legitimate.

That means, ahead of time, I know the signing secret from the payment provider and then can compare the payload to their signature. If it matches I accept the webhook event, and if it fails... erm, yeah, bye bye.

So, in my Rails application I need to check the request headers for any extra data for example X-Signature and also the request body. Then I compare the two and see if it matches the signing secret. Huh?

Here are the important parts of the request:

request.headers["X-Signature"]
request.body.read

request.body.read is basically just the payload.

Next I have a private method called check_webhook_is_valid. Here I pass in the above, which will then return either true or false.

check_webhook_is_valid(request.headers["X-Signature"], request.body.read)

Nothing too bad here, and nothing complicated — I could do some nil checks here, but I don't want to over complicate anything.

Now to the hard bit... but don't worry, I will give you the answer 😬

def check_webhook_is_valid(signature, payload)
  hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV["SIGNING_SECRET"], payload)
  hmac == signature
end

All these are built into Rails already so you don't need anything extra, I pass in the signature (which you got from request.headers["X-Signature"] and then of course the payload.

Because I'm a good citizen I use an ENV[] variable so I don't store credentials in source.

The above code calculates a hmac, which basically uses the signing secret and the payload — that's what the payment provider does on their end, and they send it through as the signature.

Then I just compare my calculated hmac signature with their signature — if they match, happy days and proceed with processing. If not :unauthorized 🥸