Verifying signatures
Why verify signatures?
Without signature verification, any party that knows your webhook endpoint URL could send it forged requests. By validating the signature you ensure that the request was sent by Simplicate, not by a third party, and that the payload has not been modified in transit.
Always verify the signature before processing a webhook payload.
Signing secret
Every webhook has a signing secret, which is generated when the webhook is created. The secret is viewable only after creation — it cannot be viewed again. Store it securely as soon as you receive it.

Simplicate signs each webhook payload using HMAC-SHA256 and sends the signature in the X-Webhook-Signature HTTP header. The header value looks like this:
sha256=a1b2c3d4e5f6...
How to verify
To confirm that a webhook request genuinely came from Simplicate:
- Read the raw request body exactly as received (do not parse or re-serialize it).
- Compute
HMAC-SHA256over the raw body, using your webhook's signing secret as the key. - Compare your computed hex digest to the value after
sha256=in theX-Webhook-Signatureheader. - If they match, the request is authentic.
The payload must match byte for byte. Even a difference in whitespace (e.g. {"test": 1} vs {"test":1}) will cause verification to fail. Always use the raw request body — never a parsed-then-serialized version.
Example implementations
PHP
$payload = file_get_contents('php://input');
$secret = 'your-webhook-secret';
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(403);
exit('Invalid signature');
}
Node.js
const crypto = require("crypto");
function verifyWebhookSignature(rawBody, secret, signatureHeader) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
Python
import hmac
import hashlib
def verify_webhook_signature(raw_body: bytes, secret: str, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)