We’re building a custom event consumer for our agent desktop backend. The goal is to catch conversation:updated events via a webhook endpoint hosted on an internal Node.js service (Express). Everything works fine when we just dump the payload to a log, but now we need to verify the signature to prevent replay attacks.
The docs mention the X-Genesys-Signature header. I’ve been trying to replicate the verification logic using crypto.createHmac('sha256', secret). The issue is that the signature in the header doesn’t match what I’m generating locally.
Here’s the relevant snippet from our route handler:
app.post('/webhook/genesys', (req, res) => {
const signature = req.headers['x-genesys-signature'];
const timestamp = req.headers['x-genesys-timestamp'];
const body = req.body;
// Constructing the string to sign
// Docs say it's HMAC-SHA256 of (timestamp + body)
const stringToSign = timestamp + JSON.stringify(body);
const hmac = crypto.createHmac('sha256', process.env.GENESYS_WEBHOOK_SECRET);
hmac.update(stringToSign);
const calculatedSignature = hmac.digest('hex');
if (calculatedSignature !== signature) {
console.log('Signature mismatch');
console.log('Expected:', signature);
console.log('Calculated:', calculatedSignature);
return res.status(403).send('Invalid signature');
}
res.status(200).send('OK');
});
The calculatedSignature is always different. I’ve tried using req.rawBody but that’s undefined in Express unless you use a raw middleware, which breaks JSON parsing. If I parse the JSON first, stringify it again, the property order might change, right? That would break the hash.
Is there a specific order for the JSON keys? Or am I constructing the stringToSign wrong? The documentation is pretty light on the exact byte-level representation expected for the body part of the signature calculation. We’re on the latest version of the platform, and the webhook is configured to send the signature header.