> This page is for Taurus PROTECT, version v3.54.
> For other versions, use one of these documentation indexes:
> - v3.58 (default): https://taurushq.ferndocs.com/protect-capital/v3.58/llms.txt
> - v3.56: https://taurushq.ferndocs.com/protect-capital/v3.56/llms.txt
> - v3.54: https://taurushq.ferndocs.com/protect-capital/v3.54/llms.txt
> - v3.52: https://taurushq.ferndocs.com/protect-capital/v3.52/llms.txt
> - v3.50: https://taurushq.ferndocs.com/protect-capital/v3.50/llms.txt
> - v3.48: https://taurushq.ferndocs.com/protect-capital/v3.48/llms.txt
> - v3.46: https://taurushq.ferndocs.com/protect-capital/v3.46/llms.txt
> - v3.44: https://taurushq.ferndocs.com/protect-capital/v3.44/llms.txt
> - v3.42: https://taurushq.ferndocs.com/protect-capital/v3.42/llms.txt
> - v3.40: https://taurushq.ferndocs.com/protect-capital/v3.40/llms.txt
> - v3.38: https://taurushq.ferndocs.com/protect-capital/v3.38/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://taurushq.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://taurushq.ferndocs.com/_mcp/server.

# 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.

> **Note**
>
> **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:

**`Python`**

```python Python
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`,.

**`Python`**

```python Python
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:

**`Python`**

```python Python
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
```