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

# Webhook Verification

> How to verify webhook signatures to ensure requests are authentic

## Overview

All Drippi webhook requests include a signature in the `X-Drippi-Signature` header that you must verify to ensure the request authentically came from Drippi and hasn't been tampered with.

## Signature Format

The signature is a HMAC-SHA256 hash of the request body using your webhook's signing secret:

```
X-Drippi-Signature: sha256=<signature>
```

## Getting Your Signing Secret

When you create a webhook endpoint, Drippi generates a unique signing secret for that endpoint. This secret is returned in the API response and should be stored securely in your application.

## Verification Examples

### Node.js

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

function verifyWebhookSignature(payload, signature, secret) {
    // Remove 'sha256=' prefix from signature
    const receivedSignature = signature.replace('sha256=', '');

    // Calculate expected signature
    const expectedSignature = crypto.createHmac('sha256', secret).update(payload, 'utf8').digest('hex');

    // Compare signatures using timing-safe comparison
    return crypto.timingSafeEqual(Buffer.from(receivedSignature, 'hex'), Buffer.from(expectedSignature, 'hex'));
}

// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const signature = req.headers['x-drippi-signature'];
    const payload = req.body;
    const secret = process.env.DRIPPI_WEBHOOK_SECRET;

    if (!verifyWebhookSignature(payload, signature, secret)) {
        console.error('Invalid webhook signature');
        return res.status(401).send('Unauthorized');
    }

    // Process the webhook
    const event = JSON.parse(payload);
    console.log('Received webhook:', event.event);

    res.status(200).send('OK');
});
```

### Python

```python theme={null}
import hashlib
import hmac
import json
from flask import Flask, request, abort

app = Flask(__name__)

def verify_webhook_signature(payload, signature, secret):
    """Verify the webhook signature"""
    # Remove 'sha256=' prefix from signature
    received_signature = signature.replace('sha256=', '')

    # Calculate expected signature
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()

    # Compare signatures using timing-safe comparison
    return hmac.compare_digest(received_signature, expected_signature)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Drippi-Signature')
    payload = request.get_data()
    secret = os.environ.get('DRIPPI_WEBHOOK_SECRET')

    if not verify_webhook_signature(payload, signature, secret):
        print('Invalid webhook signature')
        abort(401)

    # Process the webhook
    event = json.loads(payload)
    print(f'Received webhook: {event["event"]}')

    return 'OK', 200
```

### PHP

```php theme={null}
<?php
function verifyWebhookSignature($payload, $signature, $secret) {
    // Remove 'sha256=' prefix from signature
    $receivedSignature = str_replace('sha256=', '', $signature);

    // Calculate expected signature
    $expectedSignature = hash_hmac('sha256', $payload, $secret);

    // Compare signatures using timing-safe comparison
    return hash_equals($receivedSignature, $expectedSignature);
}

// Get the webhook data
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DRIPPI_SIGNATURE'] ?? '';
$secret = $_ENV['DRIPPI_WEBHOOK_SECRET'];

if (!verifyWebhookSignature($payload, $signature, $secret)) {
    error_log('Invalid webhook signature');
    http_response_code(401);
    exit('Unauthorized');
}

// Process the webhook
$event = json_decode($payload, true);
error_log('Received webhook: ' . $event['event']);

http_response_code(200);
echo 'OK';
?>
```

### Go

```go theme={null}
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "strings"
)

func verifyWebhookSignature(payload []byte, signature, secret string) bool {
    // Remove 'sha256=' prefix from signature
    receivedSignature := strings.TrimPrefix(signature, "sha256=")

    // Calculate expected signature
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expectedSignature := hex.EncodeToString(mac.Sum(nil))

    // Compare signatures using timing-safe comparison
    return hmac.Equal([]byte(receivedSignature), []byte(expectedSignature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    signature := r.Header.Get("X-Drippi-Signature")
    secret := os.Getenv("DRIPPI_WEBHOOK_SECRET")

    payload, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "Error reading request body", http.StatusBadRequest)
        return
    }

    if !verifyWebhookSignature(payload, signature, secret) {
        log.Println("Invalid webhook signature")
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }

    // Process the webhook
    var event map[string]interface{}
    if err := json.Unmarshal(payload, &event); err != nil {
        http.Error(w, "Error parsing JSON", http.StatusBadRequest)
        return
    }

    log.Printf("Received webhook: %s", event["event"])

    w.WriteHeader(http.StatusOK)
    fmt.Fprint(w, "OK")
}

func main() {
    http.HandleFunc("/webhook", webhookHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```

## Security Best Practices

### 1. Always Verify Signatures

Never process webhook payloads without first verifying the signature. This prevents malicious actors from sending fake webhook requests.

### 2. Use Timing-Safe Comparison

Use timing-safe comparison functions (like `crypto.timingSafeEqual()` in Node.js) to prevent timing attacks when comparing signatures.

### 3. Store Secrets Securely

Store your webhook signing secrets in environment variables or a secure key management system. Never hardcode them in your application.

### 4. Use HTTPS

Always use HTTPS endpoints for your webhooks to prevent man-in-the-middle attacks.

### 5. Validate Payload Structure

After verifying the signature, validate that the payload structure matches the expected webhook format.

## Troubleshooting

### Common Issues

**Signature Mismatch**

* Ensure you're using the correct signing secret for the webhook endpoint
* Verify you're using the raw request body (not parsed JSON) for signature calculation
* Check that you're removing the 'sha256=' prefix from the signature header

**Missing Signature Header**

* Ensure your webhook endpoint URL is correctly configured
* Check that you're reading the `X-Drippi-Signature` header (case-sensitive)

**Encoding Issues**

* Use UTF-8 encoding when calculating the HMAC signature
* Ensure consistent encoding between signature calculation and verification

## Testing Your Implementation

You can test your webhook verification by:

1. Creating a test webhook endpoint in the Drippi dashboard
2. Triggering test events from your automations
3. Logging successful and failed verification attempts
4. Using webhook testing tools to send sample payloads

## Next Steps

* [Webhook Events](/webhooks/events) - Learn about all available webhook events and their payloads
