Skip to main content
Every webhook request includes two headers for signature verification:
HeaderDescription
X-Klara-Signaturesha256=<hex-digest> — HMAC-SHA256 signature of the payload
X-Klara-TimestampUnix timestamp (seconds) when the webhook was sent

Verification steps

  1. Extract the timestamp and signature from headers
  2. Reject timestamps older than 5 minutes (replay protection)
  3. Compute HMAC-SHA256(secret, "{timestamp}.{raw_body}")
  4. Compare the computed signature with the received one using constant-time comparison
Always use a constant-time comparison function (e.g., crypto.timingSafeEqual in Node.js) to prevent timing attacks.

Implementation examples

import crypto from 'crypto';

const TIMESTAMP_TOLERANCE = 300; // 5 minutes in seconds

function verifyWebhookSignature(rawBody, signatureHeader, timestampHeader, secret) {
  const timestamp = parseInt(timestampHeader, 10);

  // 1. Reject stale timestamps
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestamp) > TIMESTAMP_TOLERANCE) {
    throw new Error('Webhook timestamp too old');
  }

  // 2. Compute expected signature
  const signedContent = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signedContent)
    .digest('hex');

  // 3. Constant-time comparison
  const received = signatureHeader.replace('sha256=', '');
  const isValid = crypto.timingSafeEqual(
    Buffer.from(received),
    Buffer.from(expected)
  );

  if (!isValid) {
    throw new Error('Invalid webhook signature');
  }

  return true;
}

// Express example
app.post('/webhooks/klara', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    verifyWebhookSignature(
      req.body.toString(),
      req.headers['x-klara-signature'],
      req.headers['x-klara-timestamp'],
      process.env.KLARA_WEBHOOK_SECRET
    );

    const event = JSON.parse(req.body);
    // Process event...

    res.status(200).send('OK');
  } catch (err) {
    res.status(401).send('Invalid signature');
  }
});
Make sure you use the raw request body (not a parsed/re-serialized version) when computing the signature. Re-serializing JSON can change key ordering or whitespace, which will cause verification to fail.

Testing signatures

Use the Send test button on the webhook settings page to send a test event to your endpoint. Check that your signature verification passes before subscribing to real events.