Webhooks
Webhooks Authentication
Integration Steps
For integration you need:
- Contact the Zentact team to start the integration process. They’ll provide you with a unique HMAC key. It is required and critical to authenticate incoming requests with the HMAC key.
- Create a public endpoint to receive HTTP POST requests using SSL.
- Add request signature verification logic. When comparing signatures, don’t use simple string comparison, as it is susceptible to timing attacks. Instead, use a safe equals version in your framework of choice. An example is provided below.
- Contact the Zentact team and provide the full HTTP URL of your endpoint e.g. https://api.dentalsolutions.com/zentact-webhooks . We will enable Webhook API HTTP Request sending to the provided endpoint.
Signature Validation
To protect your server from unauthorised webhook events, you must verify that the message is authentic by using Hash-based message authentication code (HMAC) signatures. Each webhook event will include a signature calculated using a secret HMAC key and a payload from the webhook. By verifying this signature, you confirm that the webhook was sent by Zentact, and was not modified during transmission.
To build HMAC validation function perform the following steps:
- Convert received HTTP body payload to a binary representation using UTF-8 charset.
- Convert the HMAC key that you received from Zentact to a binary representation, given the UTF-8 charset.
- Calculate HMAC using cryptography library in your language of choice using: The SHA256 function. The binary representation of the payload. The binary representation of the HMAC key.
- To get the final signature, Base64-encode the result. Compare the signatures generated in step 3 and from received
x-hmac-signature
HTTP header using Timing attack safe comparison method.
TypeScript example:
import * as crypto from "crypto";
const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET;
const verify_signature = (req: Request) => {
const trustedSignature = crypto
.createHmac("sha256", Buffer.from(WEBHOOK_SECRET, 'hex'))
.update(req.body, 'utf8')
.digest("base64");
const untrustedSignature = req.headers.get("x-hmac-signature");
return return stringsSafeEqual(trustedSignature, untrustedSignature);
};
const stringsSafeEqual = (a, b) => {
return a.length === b.length && crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
};
const handleWebhook = (req: Request, res: Response) => {
if (!verify_signature(req)) {
res.status(401).send("Unauthorized");
return;
}
// The rest of your logic here
};
Python Example
import hashlib
import hmac
def verify_signature(data, hmac_secret_hex, signature_header):
"""Verify that the payload was sent from Zentact by validating SHA256.
Raise and return 403 if not authorized.
Args:
payload_body: original request body to verify (request.body())
secret_token: Zentact app webhook token (WEBHOOK_SECRET)
signature_header: header received from Zentact (x-hmac-signature)
"""
if not signature_header:
raise HTTPException(status_code=403, detail="x-hmac-signature header is missing!")
# Decode the hex encoded secret to bytes
secret_key = bytes.fromhex(hmac_secret_hex)
# Decode the provided base64 signature to bytes
provided_signature_bytes = base64.b64decode(signature_header)
# Create a new HMAC object using the secret key and the SHA256 hash function
hmac_obj = hmac.new(secret_key, data.encode(), hashlib.sha256)
# Generate the HMAC signature and encode it in base64
generated_signature_bytes = hmac_obj.digest()
if not hmac.compare_digest(generated_signature_bytes, provided_signature_bytes):
raise HTTPException(status_code=403, detail="Request signatures didn't match!")