Skip to main content

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

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

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
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

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 - Learn about all available webhook events and their payloads