Webhook Signature Validation
How to verify the authenticity of incoming webhook requests
Every webhook request from Caliza includes an HMAC-SHA256 signature so you can verify that the payload is authentic and has not been tampered with. This guide walks through the verification process with implementation examples.
For webhook setup and configuration, see Webhooks. For the full list of events, see Webhook Events.
How it works
- When Caliza sends a webhook, we compute an HMAC-SHA256 hash of the raw request body using your webhook secret.
- The resulting signature is Base64-encoded and included in the
X-Caliza-Webhook-Signatureheader. - Your application recomputes the signature using the same secret and compares it to the received header.
Verification steps
- Extract the signature from the
X-Caliza-Webhook-Signatureheader. - Read the raw request body — it must be the exact, unmodified payload. Do not parse and re-serialize the JSON before verifying.
- Compute the HMAC-SHA256 of the raw body using your webhook secret.
- Base64-encode the result and compare it to the received signature.
Implementation examples
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Base64;
import org.apache.commons.io.IOUtils;
public class WebhookSignatureVerifier {
public boolean verifySignature(HttpServletRequest httpRequest, String secret)
throws Exception {
String payload = IOUtils.toString(httpRequest.getReader());
String receivedSignature = httpRequest.getHeader("X-Caliza-Webhook-Signature");
Mac sha256HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256HMAC.init(secretKey);
byte[] hash = sha256HMAC.doFinal(payload.getBytes());
String calculatedSignature = Base64.getEncoder().encodeToString(hash);
return MessageDigest.isEqual(
calculatedSignature.getBytes(),
receivedSignature.getBytes()
);
}
}Node.js (native http)
const http = require('http');
const crypto = require('crypto');
const secret = 'your_webhook_secret';
const server = http.createServer((req, res) => {
if (req.url === '/webhook' && req.method === 'POST') {
let rawBody = '';
req.on('data', chunk => { rawBody += chunk.toString(); });
req.on('end', () => {
const receivedSignature = req.headers['x-caliza-webhook-signature'];
const hmac = crypto.createHmac('sha256', secret);
hmac.update(rawBody);
const calculatedSignature = hmac.digest('base64');
const isValid = calculatedSignature === receivedSignature;
res.writeHead(isValid ? 200 : 401);
res.end(JSON.stringify({ valid: isValid }));
});
} else {
res.writeHead(404);
res.end();
}
});
server.listen(3000);Express.js
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();
const secret = 'your_webhook_secret';
// Capture the raw body before parsing
app.use(bodyParser.json({
verify: (req, res, buf, encoding) => {
if (buf && buf.length) {
req.rawBody = buf.toString(encoding || 'utf8');
}
}
}));
app.post('/webhook', (req, res) => {
const receivedSignature = req.headers['x-caliza-webhook-signature'];
const hmac = crypto.createHmac('sha256', secret);
hmac.update(req.rawBody);
const calculatedSignature = hmac.digest('base64');
const isValid = calculatedSignature === receivedSignature;
res.status(isValid ? 200 : 401).json({ valid: isValid });
});
app.listen(3000);Python
import hmac
import hashlib
import base64
def verify_signature(payload: bytes, received_signature: str, secret: str) -> bool:
computed = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).digest()
calculated_signature = base64.b64encode(computed).decode()
return hmac.compare_digest(calculated_signature, received_signature)Managing your webhook secret
- View your secret in the Backoffice under Avatar Icon > Profile Page.
- Regenerate your secret by calling
POST /v1/integrators/{integratorId}/update-webhook-secret. - Retrieve your encrypted secret by calling
GET /v1/integrators/{integratorId}/webhook-secret.
Best practices
- Store secrets securely. Never hardcode them in source code or store them in plain text. Use environment variables or a secrets manager.
- Limit access. Only team members who configure webhook processing should have access to the secret.
- Rotate regularly. Periodically regenerate your secret and update your verification logic.
- Always verify before processing. Reject any request where the signature does not match — do not process the payload.
- Use constant-time comparison. The examples above use
MessageDigest.isEqual(Java) andhmac.compare_digest(Python) to prevent timing attacks.
Updated 7 days ago
