Verifying Webhook Signatures
To guarantee that incoming webhook requests are genuinely sent by SettleSettle and have not been intercepted or manipulated, you must verify the signature headers of every HTTP POST payload.
SettleSettle signs each webhook request using HMAC SHA256 of the raw request body, keyed by the SHA256 hash of your webhook secret (Rule 5).
Signature Verification Headers
Every webhook delivery contains the following signature header:
x-settlesettle-signature**: The SHA256 signature calculated from the raw payload buffer.
Verification Algorithm
To verify the payload signature:
- Read the **
x-settlesettle-signature** header. - Read the raw request body buffer (do not parse or modify JSON formatting before hashing).
- Compute the SHA256 hash of your webhook secret (
wh_sec_...) in hex format. - Generate the expected HMAC SHA256 of the raw body using the computed secret hash as the key.
- Securely compare the header signature with the computed signature using a constant-time comparison to prevent timing attacks.
Concrete Node.js Express Example
Here is a ready-to-run middleware blueprint for verifying webhook signatures:
javascript
const crypto = require('crypto');
function verifySettleSettleWebhook(req, res, next) {
const signature = req.headers['x-settlesettle-signature'];
const webhookSecret = process.env.SETTLESETTLE_WEBHOOK_SECRET; // wh_sec_...
if (!signature || !webhookSecret) {
return res.status(401).send('Unauthorized: Missing signature or webhook secret');
}
// 1. Get raw request body buffer
const rawBody = req.rawBody; // Make sure express.raw() is configured
// 2. Compute SHA256 hash of the webhook secret key
const hashedSecret = crypto
.createHash('sha256')
.update(webhookSecret)
.digest('hex');
// 3. Generate expected HMAC SHA256 signature of the raw payload
const computedSignature = 'sha256=' + crypto
.createHmac('sha256', hashedSecret)
.update(rawBody)
.digest('hex');
// 4. Secure constant-time comparison
const signatureBuffer = Buffer.from(signature);
const computedBuffer = Buffer.from(computedSignature);
if (signatureBuffer.length !== computedBuffer.length ||
!crypto.timingSafeEqual(signatureBuffer, computedBuffer)) {
return res.status(400).send('Invalid Webhook Signature');
}
next();
}