> ## Documentation Index
> Fetch the complete documentation index at: https://docs.khaime.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Signature Verification

> Verify webhook authenticity with HMAC-SHA256.

# Webhook Signature Verification

Every webhook request includes an `X-Khaime-Signature` header containing an HMAC-SHA256 hash of the **raw request body**, signed with your webhook secret.

**Always verify signatures** before processing webhooks to prevent spoofed events.

## How It Works

1. Khaime computes `HMAC-SHA256(webhook_secret, raw_request_body)`
2. Sends the hex digest in `X-Khaime-Signature`
3. You recompute the same hash using the **raw request body** and compare

<Warning>
  Use the **raw request body** (not a re-serialized version) for signature verification. Re-serializing JSON can change key ordering or whitespace, causing verification to fail.
</Warning>

## Implementation

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifyWebhook(rawBody, signature, secret) {
    const expected = crypto
      .createHmac('sha256', secret)
      .update(rawBody)
      .digest('hex');

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

  // In Express, use express.raw() to get the raw body:
  app.post('/webhooks/khaime', express.raw({ type: 'application/json' }), (req, res) => {
    const rawBody = req.body.toString();
    const signature = req.headers['x-khaime-signature'];

    if (!verifyWebhook(rawBody, signature, process.env.KHAIME_WEBHOOK_SECRET)) {
      return res.status(401).send('Invalid signature');
    }

    const payload = JSON.parse(rawBody);
    // Process event...
    res.status(200).send('OK');
  });
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_webhook(raw_body: str, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode('utf-8'),
          raw_body.encode('utf-8'),
          hashlib.sha256
      ).hexdigest()
      return hmac.compare_digest(signature, expected)
  ```

  ```php PHP theme={null}
  function verify_khaime_webhook(string $raw_body, string $signature, string $secret): bool {
      $expected = hash_hmac('sha256', $raw_body, $secret);
      return hash_equals($expected, $signature);
  }

  // Get the raw body:
  $raw_body = file_get_contents('php://input');
  $signature = $_SERVER['HTTP_X_KHAIME_SIGNATURE'];

  if (!verify_khaime_webhook($raw_body, $signature, WEBHOOK_SECRET)) {
      http_response_code(401);
      exit('Invalid signature');
  }

  $payload = json_decode($raw_body, true);
  ```
</CodeGroup>

<Warning>
  Use **constant-time comparison** (`timingSafeEqual`, `compare_digest`, `hash_equals`) to prevent timing attacks. Never use `===` or `==` for signature comparison.
</Warning>
