Validating a webhook response

Kintaba sends a signature with every Webhook request that you can optionally use to validate requests came from Kintaba.

We send webhook signatures under the X-KINTABA-SIGNATURE header, and they look like this:

t=1629902182,
 v1=ac707831618bc7d68af35577b5b775dd9004f0da493165ec3b06699653e4d571

🚧

The example above shows the signature on two lines, but note that the actual signature comes on one line with no whitespace.

Verifying signatures

Kintaba generates signatures using HMAC with SHA256. You'll need some utility for generating proper HMACs.

Step 1: Extract the signature and timestamp

Extract the timestamp and the signature from the response header by splitting on the ,, then for each respective field, split on = to extract the value from each key-value pair.

The value for t is the timestamp, and the value for v1 is the signature.

Step 2: Create the payload to sign and verify

Create the signed_payload string by concatenating:

  • The timestamp (the value under t as extracted in Step 1)
  • The literal character .
  • The request body (i.e. the JSON payload)

Step 3: Extract the secret key from Kintaba

You'll need the webhook URL's secret key to validate the signature. To do so, navigate to the automation with the webhook in question, and click the icon to open the webhook's configuration modal:

Inside the modal, you can click the eye button to view the secret, copy this secret:

Step 4: Use the secret key and signing payload to generate a signature

Using the signing key extracted in step 3 and the payload generated in step 2, generate an HMAC with SHA-256 with the signing key as the secret and the payload as the message.

Step 5: Validate the signature and timestamp

Compare the value under the v1 field in step 2 with the signature just generated. They should match.

Additionally, to prevent replay attacks, where an attacker captures a valid webhook request and replays it against your webhook URL over and over again, you should check the timestamp extracted in step 2 and ensure it isn't too far in the past (5 minutes is a good target).

If everything matches, congratulations, you've validated your webhook!

Example verification code

Python

#!/usr/bin/env python3

# Verifies signatures for Kintaba webhooks.
# note: the signature lives in the 'x-kintaba-signature' header.

import hmac
from hashlib import sha256

def _get_timestamp_and_sig(header):
    items = [i.split("=", 2) for i in header.split(",")]
    timestamp = int([i[1] for i in items if i[0] == "t"][0])
    signature = [i[1] for i in items if i[0] == "v1"][0]
    return timestamp, signature

def _create_sig(payload, secret):
    mac = hmac.new(
        secret.encode("utf-8"),
        msg=payload.encode("utf-8"),
        digestmod=sha256,
    )
    return mac.hexdigest()

def verify_payload(payload, header, secret, tolerance_in_secs=None):
    timestamp, signature = _get_timestamp_and_sig(header)

    if not signature:
        return false

    signed_payload = "%d.%s" % (timestamp, payload)
    computed_sig = _create_sig(signed_payload, secret)

    if computed_sig != signature:
        return False

    if tolerance_in_secs and timestamp < time.time() - tolerance_in_secs:
        return False

    return True