Skip to main content

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.

Webhook secret shown after creation

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:

  1. Read the raw request body exactly as received (do not parse or re-serialize it).
  2. Compute HMAC-SHA256 over the raw body, using your webhook's signing secret as the key.
  3. Compare your computed hex digest to the value after sha256= in the X-Webhook-Signature header.
  4. If they match, the request is authentic.
caution

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)