Call Signature

The call’s ID, the timestamp of the call attempt, and the call’s payload are aggregated into a dot separated string to produce the signature. The ID can be used to determine whether you've seen the message before, and the call's timestamp can be used to impose a window of validity. Both of these together help protect the receiving application from replay attacks.

Example of the call’s signed data:

# [x-webhook-id].[x-webhook-timestamp].[payload]
0009728d-e612-4434-93bf-48e47b2f0fd3.1715616466.{"type":"currencyStatus.updated","timestamp":"2024-05-13T16:07:43.79968Z","data":{"currency":"Bitcoin Cash","status":"enabled"}}

We use this to produce an HMAC-256 signature and encode it to base64 using the webhook’s secret in the plaintext format that it was communicated in. The v1, prefix in our signature header indicates that this is a symmetric signature.

📘

Tip

Additions can later be made to this field, be sure to handle it as a space delimited list. Asymmetric key signatures could later be added with a v1a, prefix to produce a header value such as v1,[symmetric signature] v1a,[asymmetric signature]

Example of signature verification

Given an example webhook call:

# Headers:
  x-webhook-id: 	        485a79b0-13f6-43ab-a9b8-ce5b31cdade1 
  x-webhook-timestamp: 	1717490117
  x-webhook-signature: 	v1,+y43Ke7KJyZT2jopc6C6KLRn09Zp01ZqLjiJo8qFYCo= 

# Payload:
{
  "type": "currencyStatus.updated",
  "createdAt": "2024-06-04T08:35:15.442268Z",
  "data": {
    "currencyId": "abc123",
    "currency": "Bitcoin",
    "status": "enabled"
  }
}

You should verify that:

  1. The x-webhook-signature value matches the signature that you produce using the dot separated notation shown above.
  2. The x-webhook-timestamp value is within a window of validity such as 30 seconds.
  3. The x-webhook-id value has not been seen before, at least within that window of validity.

Once you have received a webhook call and extracted the headers listed above, you can produce your own signature and compare it to what you received. Here’s a trivial Python example:

def sign(id, timestamp, payload):
    # Produce the signable dot separated byte string
    signable = '.'.join((id, timestamp, payload)).encode()
    # Produce the HMAC-SHA256 signature using our webhook secret as a key
    b_sig = hmac.new(_webhook_secret.encode(), signable, hashlib.sha256).digest()
    # Encode the byte string signature into a base64 string
    return base64.b64encode(b_sig).decode('utf-8')

Remember that the x-webhook-signature value contains a list. We must extract the signature in the specific format that we’re interested in. Currently we only support symmetric key signatures that are indicated with v1,.

def extract_signature_v1(signature_header):
    # Split the signature list
    sigs = signature_header.split(' ')
    for sig in sigs:
        # Identify the signature containing the 'v1' prefix that we want
        (version, signature) = sig.split(',')
        if version == 'v1':
            return signature
    return ''

We can now compare the desired signature that we produced with the signature that we received in the header:

def handle_webhook_call(request):
    headers = request.headers
    payload = request.data.decode('utf-8')
    id_header = headers['x-webhook-id']
    timestamp_header = headers['x-webhook-timestamp']
    signature_header = headers['x-webhook-signature']
    if '' in (id_header, timestamp_header, signature_header):
        return 'missing call values', 400
    receivedSignature = extract_signature_v1(signature_header)
    expectedSignature = sign(id_header, timestamp_header, payload)
    if receivedSignature == expectedSignature:
        return 'call OK', 200
    else:
        return 'invalid webhook call signature', 401
    return '', 500



  © 2025 Taurus SA. All rights reserved.