Skip to main content

Webhook Security

Every webhook request from ConsentForge is signed with HMAC-SHA256. Always verify the signature before processing the payload — this confirms the request came from ConsentForge and the body was not tampered with.

Request headers

HeaderDescription
X-ConsentForge-SignatureHMAC-SHA256 hex digest of {timestamp}.{raw_body}
X-ConsentForge-TimestampUnix timestamp (seconds) when the event was sent
X-ConsentForge-Delivery-IDUnique delivery ID — use for idempotency

Verification algorithm

  1. Read X-ConsentForge-Timestamp from the request headers
  2. Read X-ConsentForge-Signature from the request headers
  3. Build the signing string: {timestamp}.{raw_request_body} (concatenate with a dot)
  4. Compute HMAC-SHA256 of the signing string using your webhook secret
  5. Compare (using timing-safe comparison) with the received signature
  6. Reject if the timestamp is more than 5 minutes old (replay protection)
warning

Always use timing-safe comparison (e.g. hash_equals, crypto.timingSafeEqual, hmac.compare_digest). Standard string comparison is vulnerable to timing attacks.

Code examples

const crypto = require('crypto');

function verifyConsentForgeWebhook(rawBody, signature, timestamp, secret) {
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return false; // Replay protection: reject if older than 5 minutes
}

const signingString = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signingString)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
);
}

// Express endpoint
app.post('/webhooks/consentforge', express.raw({ type: '*/*' }), (req, res) => {
const valid = verifyConsentForgeWebhook(
req.body.toString(),
req.headers['x-consentforge-signature'],
req.headers['x-consentforge-timestamp'],
process.env.CONSENTFORGE_WEBHOOK_SECRET
);

if (!valid) return res.status(401).json({ error: 'Invalid signature' });

const payload = JSON.parse(req.body);
console.log('Received event:', payload.event);
res.json({ received: true });
});

Secret rotation

To rotate your webhook secret without downtime:

  1. Go to Dashboard → Property → Webhooks → [Webhook] → Rotate Secret
  2. A new secret is issued — your old secret remains valid for 24 hours
  3. Update your server to accept the new secret
  4. After 24 hours the old secret is invalidated automatically

Finding your webhook secret

Your webhook secret is shown once at creation time. If you've lost it, rotate it:

Dashboard → Property → Webhooks → [Webhook] → Rotate Secret

Testing signature verification

Use the Send Test Event button in the Dashboard to send a real signed payload to your endpoint. Check your server logs to confirm successful verification.