Capvant

Signature verification

Verify the signature on a webhook before you trust its body.

Every delivery is signed so you can verify it came from Capvant. The X-Capvant-Signature header carries an HMAC-SHA256 of the raw request body, keyed with your endpoint's secret:

Shell
X-Capvant-Signature: sha256=6c1f0b...

Replay protection

Two more headers bind the send time to the body: X-Capvant-Timestamp (unix seconds) and X-Capvant-Signature-256: t=<timestamp>,v1=<hmac>, where v1 is an HMAC-SHA256 over the string <timestamp>.<raw body> with the same secret. Verify v1 and refuse deliveries whose timestamp is older than your tolerance (five minutes is the usual choice); a captured delivery then cannot be replayed against you. Retries of the same event carry a fresh timestamp and signature. A delivery sent again through the replay endpoint carries X-Capvant-Signature only for now, so keep the first check for replays. Capvant never follows a redirect from your endpoint: answer 2xx directly on the registered URL.

Verify (Node)

Node
import { createHmac, timingSafeEqual } from 'crypto';

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const given = Buffer.from(header ?? '');
  const wanted = Buffer.from(expected);
  return given.length === wanted.length && timingSafeEqual(given, wanted);
}

Verify the timestamped signature (Node)

Node
import { createHmac, timingSafeEqual } from 'crypto';

function verifyV1(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = createHmac('sha256', secret).update(parts.t + '.' + rawBody).digest('hex');
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  const wanted = Buffer.from(expected, 'hex');
  return given.length === wanted.length && timingSafeEqual(given, wanted);
}

Verify (Python)

Python
import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(header, expected)

Compute the HMAC over the raw bytes before any JSON parsing; re-serialised JSON will not match.

On this page