Securing Webhook Requests

While creating a new webhook, we have an option to provide a Secret key. The secret key is an optional field. Adding a secret key ensures that the webhook is genuinely from the neeto and not from a hacker.

Setting up the secret key on the server

You should set up an environment variable on the server that stores the secret key. Typically, this is as simple as running the following command.

export SECRET_KEY=YOUR-SECRET-KEY

Validating requests are from your neeto product

When your secret key is set, your neeto product uses that secret key to create a hash signature with each payload. This hash signature is included with the headers of each request as x-neeto-webhook-signature.

You should calculate a hash using your SECRET_KEY, and ensure that the result matches the hash from your neeto product.

Your language and server implementations may differ from the following examples. However, there are a number of very important things to point out:

  • No matter which implementation you use, the hash signature starts with sha256=.

  • Using a plain == operator is not advised. A method like secure_compare performs a "constant time" string comparison, which helps mitigate certain timing attacks against regular equality operators.

Ruby example:

For example, you can define the following verify_signature function:

def verify_signature(payload_body)
  signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_KEY'], payload_body)
  return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_NEETO_CAL_SIGNATURE_256])
end

Then you can call it when you receive a webhook payload:

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

Python example:

For example, you can define the following verify_signature function and call it when you receive a webhook payload:

import hashlib
import hmac
def verify_signature(payload_body, secret_key, signature_header):
    """Verify that the payload was sent from your neeto product by validating SHA256.
    
    Raise and return 403 if not authorized.
    
    Args:
        payload_body: original request body to verify (request.body())
        secret_key: neeto product webhook secret key (WEBHOOK_SECRET)
        signature_header: header received from neeto product (x-neeto-webhook-signature)
    """
    if not signature_header:
        raise HTTPException(status_code=403, detail="x-neeto-webhook-signature header is missing!")
    hash_object = hmac.new(secret_key.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
    expected_signature = "sha256=" + hash_object.hexdigest()
    if not hmac.compare_digest(expected_signature, signature_header):
        raise HTTPException(status_code=403, detail="Request signatures didn't match!")

Can't find what you're looking for?