Every webhook Simiz sends includes a HMAC SHA-256 signature in the X-Simiz-Signature header. You should always verify this signature before processing the event.

How it works

  1. Simiz computes the signature using your webhook secret and the signed payload ${timestamp}.${raw_body}
  2. The signature is sent in the X-Simiz-Signature header as t=<unix_timestamp>,v1=<hmac_sha256_hex>
  3. On your side : parse the header, verify timestamp is within 5 min, recompute HMAC on ${timestamp}.${raw_body}, timing-safe compare

Verification examples

import crypto from 'crypto';

function verifyWebhook(
  payload: string,
  header: string,
  secret: string
): boolean {
  const parts = header.split(',');
  let timestamp = '';
  let signature = '';

  for (const part of parts) {
    const [key, value] = part.split('=');
    if (key === 't') timestamp = value;
    if (key === 'v1') signature = value;
  }

  // Reject if timestamp is older than 5 minutes
  const fiveMinutes = 5 * 60 * 1000;
  if (Date.now() - parseInt(timestamp) * 1000 > fiveMinutes) {
    return false;
  }

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

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

// Express example
app.post('/webhooks/simiz',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const header = req.headers['x-simiz-signature'] as string;

    if (!verifyWebhook(req.body.toString(), header, process.env.WEBHOOK_SECRET)) {
      return res.status(401).send('Invalid signature');
    }

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

    res.status(200).json({ received: true });
  }
);

Security best practices

Always use timingSafeEqual (or equivalent) for signature comparison. Standard string comparison (===) is vulnerable to timing attacks.
  • Use the raw body — Don’t parse JSON before verifying. Middleware like express.json() can alter the payload
  • Rotate secrets — Regenerate your webhook secret periodically via the Dashboard
  • Use HTTPS — Never expose webhook endpoints over plain HTTP
  • Validate event types — Only process events you expect