› compliance_grid.aiCOMPLIANCEGRIDGOV · COMPLIANCE · INTELLIGENCE
Help Center/Webhooks & Events

Verifying webhook signatures

4 min read Updated October 22, 2026

Why verify

The signature proves a request came from ComplianceGrid and was not altered. Always verify it in production.

How it is built

The X-ComplianceGrid-Signature header looks like t=1792670696,v1=. The v1 value is the HMAC-SHA256, in hex, of the string . using your webhook secret. Reject requests whose timestamp is more than 5 minutes from your clock (replay protection).

Node.js

javascript
import crypto from 'crypto';

function verifyWebhook(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;

  const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post('/webhooks/compliancegrid', express.raw({ type: 'application/json' }), (req, res) => {
  const ok = verifyWebhook(req.body.toString('utf8'), req.headers['x-compliancegrid-signature'], process.env.WEBHOOK_SECRET);
  if (!ok) return res.status(401).end();
  // handle the event
  res.status(200).end();
});

Python

python
import hmac, hashlib, time

def verify_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split('=', 1) for p in header.split(','))
    t, v1 = parts.get('t'), parts.get('v1')
    if not t or not v1 or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Notes

  • Use a constant-time comparison
  • Verify against the raw bytes, before parsing
  • The secret is returned once when it is generated; if you supplied your own, keep it in a secrets manager

Was this article helpful?