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

# Verify InfraAudit Webhook Signatures

> Every InfraAudit webhook delivery includes an HMAC-SHA256 signature header. Learn how to verify it in Go, Node.js, and Python to prevent spoofed requests.

Every webhook delivery from InfraAudit includes an `X-InfraAudit-Signature` header. Verifying this header before processing the payload confirms the request came from InfraAudit and was not tampered with in transit.

## Header format

```
X-InfraAudit-Signature: sha256=<hex-encoded-hmac>
```

The signature is an HMAC-SHA256 of the raw request body, computed with the webhook's secret key. The secret is shown once when you create the webhook — store it securely.

## Verification examples

<CodeGroup>
  ```go Go theme={null}
  import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "net/http"
  )

  func verifySignature(r *http.Request, body []byte, secret string) bool {
    signature := r.Header.Get("X-InfraAudit-Signature")
    if signature == "" {
      return false
    }
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expected))
  }
  ```

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

  function verifySignature(body, secret, signature) {
    if (!signature) return false
    const hmac = crypto.createHmac('sha256', secret)
    hmac.update(body, 'utf8')
    const expected = 'sha256=' + hmac.digest('hex')
    return crypto.timingSafeEqual(
      Buffer.from(expected, 'utf8'),
      Buffer.from(signature, 'utf8')
    )
  }

  // Express example
  app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const sig = req.headers['x-infraaudit-signature']
    if (!verifySignature(req.body, process.env.WEBHOOK_SECRET, sig)) {
      return res.status(401).send('Invalid signature')
    }
    const payload = JSON.parse(req.body)
    // process payload...
    res.sendStatus(200)
  })
  ```

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

  def verify_signature(body: bytes, secret: str, signature: str) -> bool:
      if not signature:
          return False
      mac = hmac.new(secret.encode(), body, hashlib.sha256)
      expected = 'sha256=' + mac.hexdigest()
      return hmac.compare_digest(expected, signature)

  # Flask example
  @app.route('/webhook', methods=['POST'])
  def webhook():
      body = request.get_data()
      signature = request.headers.get('X-InfraAudit-Signature', '')
      if not verify_signature(body, WEBHOOK_SECRET, signature):
          abort(401)
      payload = request.get_json(force=True)
      # process payload...
      return '', 200
  ```
</CodeGroup>

## Important: use the raw request body

The signature is computed against the **raw** request body bytes, before JSON parsing. Read the raw bytes before your framework parses the body:

* **Express**: use `express.raw({ type: 'application/json' })` on the route
* **Flask**: use `request.get_data()` before `request.get_json()`

<Warning>
  Using a parsed/re-serialized body for verification will fail — whitespace changes and key ordering differences will produce a different HMAC.
</Warning>

## Use timing-safe comparison

Always use a timing-safe comparison function to prevent timing attacks:

* Go: `hmac.Equal`
* Node.js: `crypto.timingSafeEqual`
* Python: `hmac.compare_digest`

Standard string equality (`==`) is not safe for this purpose.

## Rotating the webhook secret

To rotate a webhook secret:

1. Delete the existing webhook.
2. Create a new webhook with the same URL and event subscriptions. A new secret is generated.
3. Update your receiver with the new secret.

For SaaS Enterprise customers, contact support to rotate the secret in place without recreating the webhook.
