const crypto = require('crypto');
function verifySwitchFlowSignature(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(',').map((kv) => kv.split('='))
);
const { t, v1 } = parts;
if (!t || !v1) return false;
// Bound the replay window to ±5 minutes
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return (
expected.length === v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
);
}
// Express: capture the raw body for the webhook route
app.post(
'/webhooks/switchflow',
express.raw({ type: 'application/json' }),
(req, res) => {
const ok = verifySwitchFlowSignature(
req.body.toString('utf8'),
req.get('SwitchFlow-Signature') || '',
process.env.SWITCHFLOW_WEBHOOK_SECRET
);
if (!ok) return res.status(400).send('invalid signature');
const event = JSON.parse(req.body);
// enqueue for async processing, keyed on event.id
res.sendStatus(200);
}
);