Skip to main content

Securing Webhooks Endpoints

Once your server is configured to receive webhook events, it will listen for any payload sent to the endpoint you configured. Bold Penguin recommends securing your endpoint using end-to-end encryption via HTTPS and a secret token to validate the payloads you receive.

You'll need to set up your secret token in two places: the Webhooks API and your endpoint server.

Setting a secret token using the Webhooks API

To set a secret token using the Webhooks API:

  1. Create your secret token by generating a random string with high entropy. For example, by taking the output of ruby -rsecurerandom -e 'puts SecureRandom.hex(20)' on the terminal)

  2. Add the secret token to your webhook subscription using the webhook update API. Remember that you do not need to include the full configuration in your request body, just the secret:

    {
    "secret": "503bec91ecdf413562f69336b597e5b78550f548"
    }

Setting a secret token on your server

We recommend storing your secret token on the endpoint server as an environment variable. On many systems this is as simple adding the following line to your shell configuration:

export SECRET_TOKEN=[your_token]

Note: For security reasons, never hardcode the secret token into your application.

Validating payloads from Bold Penguin

When your secret token is set, Bold Penguin uses it to create a hash signature for each event payload.

This hash signature is passed along with each request in the X-Signature HTTP header. Suppose you have a basic server listening to webhooks that looks like this:

require 'sinatra'
require 'json'

post '/payload' do
push = JSON.parse(params[:payload])
"I got some JSON: #{push.inspect}"
end

The goal is to compute a hash using your SECRET_TOKEN, and ensure that the hash from Bold Penguin matches. Bold Penguin uses an HMAC hexdigest to compute the hash, so you could change your server to look a little like this:

post '/payload' do
request.body.rewind
payload_body = request.body.read
verify_signature(payload_body)
push = JSON.parse(params[:payload])
"I got some JSON: #{push.inspect}"
end

def verify_signature(payload_body)
signature = OpenSSL::HMAC.hexdigest('SHA256', ENV['SECRET_TOKEN'], payload_body)
return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_SIGNATURE'])
end

Your language and server implementations may differ from these examples. A few core requirements of any signature validation implementation are:

  • Alway use your secret token and the raw payload body from the event to compute the SHA256 hash to avoid whitespace issues
  • Make sure you are using the hexdigest HMAC for your language (not Base64 or other base)
  • To prevent time attacks, avoid the comparison (==) for signature validation
  • Use a method like secure_compare that performs a constant time string comparison

IP Whitelisting

A less secure approach to securing your webhook endpoint is by whitelisting the IP address space used by Bold Penguin. Bold Penguin does not recommend this approach as IP addresses can change and even be impersonated by malicious parties.

If whitelisting is your only alternative for securing your endpoint, Bold Penguin maintains a list of current IP space for all environments.

You will need to whitelist all IP space for our Beta and Production environments.