Integrations

Verifying Webhook Signatures

Validate that incoming webhooks are genuinely from Diaform using HMAC-SHA256 signatures.

When you receive a webhook from Diaform, you should verify that it's authentic and hasn't been tampered with. This page explains how to validate webhook signatures using the secret generated when you created your webhook.

Why Verify Signatures?#

Signature verification ensures:

  • The webhook payload hasn't been tampered with
  • The request genuinely comes from Diaform
  • An attacker can't impersonate Diaform and send malicious payloads to your endpoint

How It Works#

When Diaform sends a webhook, it:

  1. Computes an HMAC-SHA256 hash of the JSON payload using your webhook secret as the key
  2. Converts the hash to a hexadecimal string
  3. Sends this signature in the X-Webhook-Signature header

Your server should perform the same computation and compare the results.

Verification Steps#

To verify a webhook signature:

  1. Get the raw request body as a string (before parsing it as JSON)
  2. Compute the HMAC-SHA256 hash of the body using your webhook secret
  3. Convert the hash to a hexadecimal string
  4. Compare your computed signature with the X-Webhook-Signature header
  5. Use a timing-safe comparison function to prevent timing attacks

Never expose your webhook secret in client-side code or version control. Store it securely in environment variables or a secrets manager on your server.

Node.js Example#

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your Express handler:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const isValid = verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(req.body);
  // Process the webhook...
  res.status(200).send('OK');
});

Important: Use express.raw() middleware to get the raw request body. If you parse the body as JSON before verification, the signature check will fail because JSON parsing can change whitespace and key order.

Python Example#

import hmac
import hashlib

def verify_signature(payload_body, signature, secret):
    expected = hmac.new(
        secret.encode('utf-8'),
        payload_body.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# In your Flask handler:
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Webhook-Signature')
    payload_body = request.get_data(as_text=True)

    if not verify_signature(payload_body, signature, WEBHOOK_SECRET):
        return 'Invalid signature', 401

    payload = request.get_json()
    # Process the webhook...
    return 'OK', 200

Important: Use request.get_data(as_text=True) to get the raw body before parsing it as JSON.

Troubleshooting#

If signature verification is failing:

  • Check that you're using the raw request body — Don't parse it as JSON before verification
  • Verify you're using the correct secret — The secret is specific to each webhook endpoint
  • Ensure the secret isn't being modified — Whitespace or encoding issues can cause mismatches
  • Use timing-safe comparison — Regular string comparison (==) can leak timing information

If you continue to have issues, check your webhook logs in the Diaform dashboard for error details.