Authenticate ThriveDesk webhook requests with X-TD-SIGNATURE and constant-time comparison.
Every webhook includes X-TD-SIGNATURE. ThriveDesk computes a base64-encoded HMAC-SHA1 using the
webhook secret and the JSON-encoded data value from the request body.
X-TD-SIGNATURE header.data value without changing its key order or character escaping.import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWebhook(body, suppliedSignature, secret) {
const expected = createHmac('sha1', secret)
.update(JSON.stringify(body.data))
.digest('base64');
const supplied = Buffer.from(suppliedSignature ?? '', 'utf8');
const calculated = Buffer.from(expected, 'utf8');
return supplied.length === calculated.length &&
timingSafeEqual(supplied, calculated);
}<?php
function verifyThriveDeskWebhook(array $body, string $signature, string $secret): bool
{
$data = json_encode($body['data']);
$expected = base64_encode(hash_hmac('sha1', $data, $secret, true));
return hash_equals($expected, $signature);
}import base64
import hashlib
import hmac
import json
def verify_webhook(body: dict, supplied: str, secret: str) -> bool:
data = json.dumps(body["data"], separators=(",", ":"), ensure_ascii=False)
digest = hmac.new(secret.encode(), data.encode(), hashlib.sha1).digest()
expected = base64.b64encode(digest).decode()
return hmac.compare_digest(expected, supplied)Preserve serialization
JSON whitespace, key order, slash escaping, and non-ASCII escaping can change the signed bytes. Test your runtime against a real delivery before launch and retain the original request for debugging without logging the secret.
Return 401 for a missing signature and 403 for a mismatch. Do not enqueue or log sensitive
payload fields until verification succeeds.