Setting your secret token
Validating payloads from CRM
When your secret token is set, CRM uses it to create a hash signature with each JSON payload. This hash signature is included with the headers of each request as X-Crm-Signature.
You should calculate a hash using your SECRET_TOKEN, and ensure that the result matches the hash from CRM. CRM uses an HMAC hex digest to compute the hash.
Note:
Webhook payloads can contain unicode characters. If your language and server implementation specifies a character encoding, ensure that you handle the payload as UTF-8.
Your language and server implementations may differ from the following examples
Test your Signature
Regardless of the programming language that you use to implement HMAC verification in your code, you can use the following secret and payload values to verify that your implementation is correct.
- secret: "It's a Secret to Everybody"
- payload: "Hello, World!"
If your implementation is correct and uses the SHA-256 algorithm, the signatures that you generate should match the following signature values:
- signature: 757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
- X-Crm-Signature: sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
Code Example
def verify_signature(payload_body)
signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_256'])
end
post '/payload' do
request.body.rewind
payload_body = request.body.read
verify_signature(payload_body)
push = JSON.parse(payload_body)
"I got some JSON: #{push.inspect}"
end
<?php
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
function verify_signature(RequestInterface $req) {
$WEBHOOK_SECRET = getenv('WEBHOOK_SECRET');
$signature = hash_hmac('sha256', json_encode($req->getBody()), $WEBHOOK_SECRET);
$trusted = 'sha256=' . $signature;
$untrusted = $req->getHeaderLine('X-Crm-Signature');
return hash_equals($trusted, $untrusted);
}
function handleWebhook(RequestInterface $req, ResponseInterface $res) {
if (!verify_signature($req)) {
$res->getBody()->write('Unauthorized');
return $res->withStatus(401);
}
// The rest of your logic here
}
?>
import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
if not signature_header:
raise HTTPException(status_code=403, detail="X-Crm-Signature header is missing!")
hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
expected_signature = "sha256=" + hash_object.hexdigest()
if not hmac.compare_digest(expected_signature, signature_header):
raise HTTPException(status_code=403, detail="Request signatures didn't match!")
let encoder = new TextEncoder();
async function verifySignature(secret, header, payload) {
let parts = header.split("=");
let sigHex = parts[1];
let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };
let keyBytes = encoder.encode(secret);
let extractable = false;
let key = await crypto.subtle.importKey(
"raw",
keyBytes,
algorithm,
extractable,
[ "sign", "verify" ],
);
let sigBytes = hexToBytes(sigHex);
let dataBytes = encoder.encode(payload);
let equal = await crypto.subtle.verify(
algorithm.name,
key,
sigBytes,
dataBytes,
);
return equal;
}
function hexToBytes(hex) {
let len = hex.length / 2;
let bytes = new Uint8Array(len);
let index = 0;
for (let i = 0; i < hex.length; i += 2) {
let c = hex.slice(i, i + 2);
let b = parseInt(c, 16);
bytes[index] = b;
index += 1;
}
return bytes;
}
import * as crypto from "crypto";
const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET;
const verify_signature = (req: Request) => {
const signature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
let trusted = Buffer.from(`sha256=${signature}`, 'ascii');
let untrusted = Buffer.from(req.headers.get("X-Crm-Signature"), 'ascii');
return crypto.timingSafeEqual(trusted, untrusted);
};
const handleWebhook = (req: Request, res: Response) => {
if (!verify_signature(req)) {
res.status(401).send("Unauthorized");
return;
}
// The rest of your logic here
};