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

# Signature Verification

> Verify webhook signatures to ensure requests are from FoxReach.

## Why Verify Signatures?

Webhook signatures ensure that the payload was sent by FoxReach and hasn't been tampered with. Always verify signatures before processing webhook events.

## How It Works

Each webhook has a unique `secret` that's generated when the webhook is created. We use this secret to create an HMAC-SHA256 signature of the request body and include it in the `X-Webhook-Signature` header.

## Verification Steps

<Steps>
  <Step title="Extract the signature">
    Get the `X-Webhook-Signature` header from the incoming request.
  </Step>

  <Step title="Compute the expected signature">
    Create an HMAC-SHA256 hash of the raw request body using your webhook secret.
  </Step>

  <Step title="Compare">
    Use a constant-time comparison to check if the signatures match.
  </Step>
</Steps>

## Code Examples

### Python

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

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.HMAC(
        secret.encode("utf-8"),
        msg=payload,
        digestmod=hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, signature)


# In your webhook handler (Flask example)
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"

@app.post("/webhooks/outreach")
def handle_webhook():
    signature = request.headers.get("X-Webhook-Signature", "")
    if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
        abort(401, "Invalid signature")

    event = request.json
    # Process the event...
    return "", 200
```

### Node.js

```javascript theme={null}
const crypto = require("crypto");

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

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

// Express example
app.post("/webhooks/outreach", (req, res) => {
  const signature = req.headers["x-webhook-signature"] || "";
  const rawBody = JSON.stringify(req.body);

  if (!verifyWebhook(rawBody, signature, WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  const event = req.body;
  // Process the event...
  res.sendStatus(200);
});
```

### Go

```go theme={null}
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
)

func verifyWebhook(payload []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    signature := r.Header.Get("X-Webhook-Signature")

    if !verifyWebhook(body, signature, "your_secret") {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

    // Process the event...
    w.WriteHeader(http.StatusOK)
}
```

<Warning>
  Always use **constant-time comparison** (like `hmac.compare_digest` in Python or `crypto.timingSafeEqual` in Node.js) to prevent timing attacks.
</Warning>
