Skip to content

Signature Verification

Webhook payloads can be signed with HMAC-SHA256 so you can verify they genuinely came from Interplai. Always verify signatures in production to prevent spoofed requests.

How it works

  1. When your webhook is registered, a shared secret is generated
  2. Each delivery includes an X-Webhook-Signature header
  3. The signature is computed as sha256=HMAC-SHA256(secret, raw_body)
  4. Your endpoint recomputes the HMAC and compares it to the header value

Verification examples

Node.js

javascript
import crypto from 'crypto';

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

  return `sha256=${expected}` === signature;
}

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

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

  const payload = JSON.parse(req.body);
  // Process the webhook...
  res.status(200).json({ received: true });
});

Python

python
import hmac
import hashlib

def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(f"sha256={expected}", signature)
python
# Flask example
from flask import Flask, request, jsonify
import os

app = Flask(__name__)

@app.route("/webhook", methods=["POST"])
def webhook():
    signature = request.headers.get("X-Webhook-Signature", "")
    is_valid = verify_webhook_signature(
        request.get_data(),
        signature,
        os.environ["WEBHOOK_SECRET"],
    )

    if not is_valid:
        return jsonify({"error": "Invalid signature"}), 401

    payload = request.get_json()
    # Process the webhook...
    return jsonify({"received": True}), 200

C#

csharp
using System.Security.Cryptography;
using System.Text;

public static bool VerifyWebhookSignature(string rawBody, string signature, string secret)
{
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
    var expected = $"sha256={Convert.ToHexString(hash).ToLowerInvariant()}";

    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected),
        Encoding.UTF8.GetBytes(signature)
    );
}

Security best practices

  • Always use constant-time comparison (e.g. crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python, CryptographicOperations.FixedTimeEquals in C#) to prevent timing attacks.
  • Verify the raw request body, not a re-serialised version. JSON serialisation can change key order or whitespace.
  • Store the webhook secret securely — use environment variables or a secrets manager, never hardcode it.
  • Reject requests without a valid signature with a 401 status code.

Important

If you re-parse and re-stringify the JSON body before verifying, the signature will not match. Always verify against the exact bytes received in the HTTP request.

Interplai API v1.2.0