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

# Webhook Security

> Verify webhook authenticity with HMAC signatures

# Webhook Security

FLTR signs every webhook with HMAC-SHA256 to prove authenticity.

## Signature Header

```
X-FLTR-Signature: <hex-encoded-signature>
```

## Verification

Always verify signatures before processing webhooks.

### Python

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

def verify_webhook(secret, signature, body):
    expected = hmac.new(
        secret.encode(),
        body,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

# Usage
if not verify_webhook(WEBHOOK_SECRET, signature, request.body):
    return {"error": "Invalid signature"}, 401
```

### JavaScript

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

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

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

if (!verifyWebhook(WEBHOOK_SECRET, signature, body)) {
  return res.status(401).json({ error: 'Invalid signature' });
}
```

### Go

```go theme={null}
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

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

    return hmac.Equal([]byte(signature), []byte(expected))
}
```

## Best Practices

### Use HTTPS

FLTR only sends webhooks to HTTPS endpoints in production.

### Verify Every Request

Never trust webhook payloads without signature verification.

### Use Raw Body

Verify against the raw request body, not parsed JSON.

```python theme={null}
# ✅ Good
body = request.get_data()
verify_webhook(secret, signature, body)

# ❌ Bad
body = json.dumps(request.json)  # May not match
```

### Rotate Secrets

Rotate webhook secrets periodically (every 90 days).

### Return 200 Quickly

Process webhooks asynchronously:

```python theme={null}
@app.route('/webhooks', methods=['POST'])
def webhook():
    # Verify
    if not verify_webhook(...):
        return {}, 401

    # Queue for processing
    queue.enqueue(process_webhook, request.json)

    # Return immediately
    return {}, 200
```

## Testing

### Generate Test Signature

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

payload = {"event": "document.processed", "data": {}}
body = json.dumps(payload)

signature = hmac.new(
    b'whsec_test_secret',
    body.encode(),
    hashlib.sha256
).hexdigest()

print(f"X-FLTR-Signature: {signature}")
```

### Send Test Webhook

```bash theme={null}
curl -X POST http://localhost:5000/webhooks \
  -H "Content-Type: application/json" \
  -H "X-FLTR-Signature: abc123..." \
  -d '{"event":"document.processed","data":{}}'
```

## Troubleshooting

### Signature Mismatch

Common causes:

1. Using parsed JSON instead of raw body
2. Wrong secret
3. Extra whitespace in body
4. Character encoding issues

### Debugging

Log both signatures:

```python theme={null}
print(f"Received: {signature}")
print(f"Expected: {expected}")
```

Compare byte-by-byte to find discrepancies.
