Securing Webhooks

Ensuring that only anvyl is sending messages to your server

Adding a secret to webhooks

The webhook's create, update, and bulk import apis all take an optional secret parameter. If this parameter is set, the value will be used as the key for creating a hmac (Hashed Message Authentication Token) that will be sent with the request header x-anvyl-signature-256. If the secret is not present on the webhook the signature header will not be sent. Once this value is set it can only be changed or removed using the update api. It's value will never be returned back to you. It is your responsibility to maintain it's secrecy.

Verifying Webhooks

If the secret parameter is set, anvyl will include a x-anvyl-signature-256 header with the request. The value of the header will be sha256=[hex digest]. To validate that the message is from anvyl, you should regenerate the HMAC's hex digest using the sha256 algorithm, the secret, and the request's body. You should then verify that the calculated HMAC matches the one included in the request.

Verification Examples

The exact implementation will very depending on your application. However, we have provided some examples for common languages to act as a starting point.

// This example implments the handleWebhook method which could be used as a
// route handler in a standerd express server or added as middleware with minor
// modifications

const crypto = require('crypto');

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

const verify_signature = (req) => {
  const signature = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(JSON.stringify(req.body))
    .digest("hex");
  return `sha256=${signature}` === req.headers.get("x-anvyl-signature-256");
};

const handleWebhook = (req, res) => {
  if (!verify_signature(req)) {
    res.status(401).send("Unauthorized");
    return;
  }
  // The rest of your logic here
};
# The verify_hmac method could be added to a rails controller and called with the request body.
# It could also be added to custom middleware with minor modifications.

def verify_hmac(req_body)
	calculated_hmac = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['WEBHOOK_SECRET'], req_body)
  return 401, "Unathorized" unless Rack::Utils.secure_compare(calculated_hmac, request.env['HTTP_X_ANVYL_SIGNATURE_256'])
end