Improving webhook security with tokens
If you expose a webserver to listen to incoming webhooks, anyone who knows the URL can trigger the server response to the webhook by sending an HTTP POST request to the server. You can reduce the risk of this unauthorized triggering by defining a token for the webhook.
If you add a token to your webhook, you can validate that your XProtect installation is the actual source of the HTTP POST.
Other methods can be used to help secure the integration of the webserver with the webhooks, for example not exposing the server to the internet or using an IP whitelist.
To improve your webhook security, you must:
- Generate a secret token.
- Configure a secret token in Management Client.
- Validate incoming requests on the receiving web server.
Generate a secret token
To randomly generate a token string, you can use an online generator, for example LastPass.com or use a code-based password generator function, for example the secrets.token_hex() function in Python 3.6 or greater.
Example:
import secrets; print(secrets.token_hex(32))
The python command in the example creates a 32-byte hexadecimal text string token.
Configure a secret token in Management Client
- Copy the text string you want to use as the token to the clipboard
- In Management Client > Rules and Events > Webhooks, select the webhook you want to add a token to
- In Webhook Information > Token (optional) field: Paste the token string for the webhook.
- Click Save in the toolbar to update the webhook.
Validate incoming requests on the receiving webserver
Before processing a request, you must validate that the signature from the header matches the signature you compute.
Generate the signature using the base 64 encoded HMAC SHA-256 digest, for example:
signature = `sha256=${HMAC256(secret_token, body).toBase64().toString('utf-8')}`
Signature validation examples
The following are examples of token string generation using various programming languages: Python, Node.js and C#.

import hashlib
import hmac
import base64
def is_signature_valid(body: bytes, header_signature: str, secret_token: bytes) -> bool:
digest: bytes = hmac.HMAC(key=secret_token, msg=body, digestmod=hashlib.sha256).digest()
expected_signature: str = f"sha256={base64.b64encode(digest).decode('utf-')}"
return hmac.compare_digest(header_signature, expected_signature)

const crypto = require('crypto')
const isSignatureValid = (body, headerSignature, secretToken) => {
const digest = crypto.createHmac("sha256", secretToken).update(body).digest();
const expectedSignature = `sha256=${digest.toString('base64').toString('utf-8')}`
return crypto.timingSafeEqual(Buffer.from(headerSignature), Buffer.from(expectedSignature))
}

private static bool isSignatureValid(string data, string headerSignature, string token)
{
var hasher = new HMACSHA256(Encoding.UTF8.GetBytes(token));
var expectedSignature = $"sha256={Convert.ToBase64String(hasher.ComputeHash(Encoding.UTF8.GetBytes(data)))}";
return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expectedSignature), Encoding.UTF8.GetBytes(headerSignature));
}