---
title: Webhook security
slug: webhook-security
docTags: 
createdAt: 2026-08-19T11:57:01.530Z
---

# Signature (HMAC)

When Kindly sends data to external services (e.g. when triggering a webhook to a service owned by you), the payload will be authenticated with a [hash-based message authentication code (HMAC)](https://en.wikipedia.org/wiki/HMAC).

The key used to create the HMAC is a shared secret, and you verify it by running the algorithm yourself with the payload and the key to re-create the HMAC.

This verifies that the sender of the message knows the secret key.

## Secret key

The secret key used in the algorithm depends on which kind of request is being sent, and you can find your keys in the Kindly Platform, under your Workspace `Settings -> General -> Security`.

If you suspect that the key might have been compromised, you can revoke it and issue a new key in the web interface.

## Algorithm

The HMAC is created with the `HMAC_SHA256` algorithm, then encoded in base 64.

The HMAC is attached to the request in the `Kindly-HMAC` header. You should also read the value of the `Kindly-HMAC-algorithm` request header and verify that it matches `HMAC-SHA-256 (base64 encoded)`. If it becomes necessary to change the algorithm in the future, this value will change to let you know.

```python
request.headers["kindly-hmac"] = base64_encode(hmac_sha256(request.body, secret_key))
request.headers["kindly-hmac-algorithm"] = "HMAC-SHA-256 (base64 encoded)"
```

## Examples

Verifying a HMAC-SHA256-value should be quite simple by using a crypto library in your preferred language.

### JS/Node

```javascript
const CryptoJS = require('crypto-js');


function verifyHmac(
    receivedHmac, // received in Kindly-HMAC request header
    dataString, // received in request body (stringified JSON)
    key // shared secret, known by your service
) {
    const generatedHmac = CryptoJS.HmacSHA256(dataString, key);
    const generatedHmacBase64 = CryptoJS.enc.Base64.stringify(generatedHmac);

    return generatedHmacBase64 === receivedHmac;
}

// test the function
const receivedHmac = "uEeD0Q7eW9btdx6LFvvlpwkzQBWdbknsQkg1C27Cx7Q=";
const dataString = '{"foo":1,"bar":2}';
const key = "examplekey";

if (!verifyHmac(receivedHmac, dataString, key)) {
    throw new Error("HMAC did not authenticate");
}
```

### Python

The hashing algorithm works on `bytes`, so we make sure to do any necessary conversions from unicode `str` to `bytes`.

```python
import base64
import hashlib
import hmac


def verify_kindly_hmac(headers: dict, request_data: bytes, secret_key: str):
    # Note: HTTP headers are case insensitive
    alg = headers.get('kindly-hmac-algorithm')
    mac = headers.get('kindly-hmac')

    if not (alg and mac):
        print("HMAC was not provided")
        return False

    if alg != "HMAC-SHA-256 (base64 encoded)":
        print("Unexpected HMAC algorithm")
        return False

    msg = request_data
    key = secret_key.encode('utf-8')

    received_hmac_b64 = mac.encode('utf-8')
    generated_hmac = hmac.new(key=key, msg=msg, digestmod=hashlib.sha256).digest()
    generated_hmac_b64 = base64.b64encode(generated_hmac)

    match = hmac.compare_digest(received_hmac_b64, generated_hmac_b64)

    if not match:
        print('HMACs did not match: {} {}'.format(received_hmac_b64, generated_hmac_b64))
        return False

    return True
```

### C# / .NET 7&#x20;

Depending on your setup you may need to rewind the request body stream after consuming it (see code comment).

```csharp
public async Task<bool> VerifyHash(HttpRequest request, string signingKey)
{
    if (!request.Headers.TryGetValue("kindly-hmac-algorithm", out var hmacAlgorithm) || hmacAlgorithm != "HMAC-SHA-256 (base64 encoded)")
    {
        throw new HttpRequestException("Unexpected HMAC algorithm", null, System.Net.HttpStatusCode.BadRequest);
    }
    if (!request.Headers.TryGetValue("kindly-hmac", out var receievedHmac) || string.IsNullOrEmpty(receievedHmac))
    {
        throw new HttpRequestException("HMAC was not provided", null, System.Net.HttpStatusCode.BadRequest);
    }
    
    using var hasher = new HMACSHA256(Encoding.UTF8.GetBytes(signingKey));
    var hash = await hasher.ComputeHashAsync(request.Body);

    // Uncomment the  line below to rewind the stream if request buffering is not enabled
    // request.Body.Seek(0, SeekOrigin.Begin);
    var hash_b64 = Convert.ToBase64String(hash);

    return receievedHmac == hash_b64;
}
```

# IP Addresses

All webhook requests originating from our platform will come from one of the following static IP addresses:

```null
35.195.26.135
34.22.194.168
35.187.168.115
35.205.84.184
34.156.206.121
207.175.91.127
34.62.3.62
35.195.112.102
```

