I am implementing a secure webhook consumer for Genesys Cloud routing events using Node.js. The goal is to validate the X-Genesys-Signature header to prevent replay attacks. The documentation suggests calculating an HMAC-SHA256 hash of the payload using the shared secret and comparing it to the header value.
My implementation looks like this:
const crypto = require('crypto');
app.post('/webhook', (req, res) => {
const signature = req.headers['x-genesys-signature'];
const payload = JSON.stringify(req.body);
const secret = process.env.WEBHOOK_SECRET;
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const calculatedSignature = hmac.digest('hex');
if (signature !== calculatedSignature) {
console.error('Signature mismatch');
return res.status(403).send('Unauthorized');
}
res.status(200).send('OK');
});
The issue is that the signature verification fails intermittently. When I log the raw payload and the calculated signature, they match perfectly in local testing with static data. However, in production, the signature from the header does not match the calculatedSignature about 10% of the time. The timestamps on the events are slightly different, sometimes by a few milliseconds. I suspect the payload stringification might be sensitive to whitespace or order of keys, but JSON.stringify should produce a deterministic output for the same object.
Is there a specific format Genesys Cloud expects for the payload when calculating the signature? I have tried parsing the body as raw text instead of JSON, but that breaks the application logic. I need to ensure the webhook is secure but also reliable. The error logs show the calculated signature is always different from the header value when the failure occurs. This is causing valid events to be rejected. I have checked the shared secret multiple times and it is correct. The timezone is US/Pacific, but I don’t think that affects the HMAC calculation. Any ideas on what might be causing this drift?