Portal Community

The X-Webhook-Signature Header

Every inbound webhook request must include an X-Webhook-Signature header with an HMAC-SHA256 signature of the request body. The format is:

X-Webhook-Signature: sha256={hex-encoded-HMAC}

Computing the Signature (Sender Side)

The external system (e.g., Shopify, GitHub, your custom sender) must compute the signature using the shared secret:

# Python example for the sending system
import hmac, hashlib

secret = b"whs_sk_live_Xy9mQ3..."  # your webhook secret
body = b'{"event":"order.created","orderId":"ord-001"}'

signature = hmac.new(secret, body, hashlib.sha256).hexdigest()
headers = {
    "X-Webhook-Signature": f"sha256={signature}",
    "Content-Type": "application/json"
}
// Node.js example
const crypto = require('crypto');
const secret = 'whs_sk_live_Xy9mQ3...';
const body = JSON.stringify({ event: 'order.created', orderId: 'ord-001' });
const sig = crypto.createHmac('sha256', secret).update(body).digest('hex');
// Header: X-Webhook-Signature: sha256={sig}

Verification (BizFirstAI Side)

// ProcessEngine/Webhooks/WebhookSignatureVerifier.cs
public class WebhookSignatureVerifier
{
    public bool Verify(string rawBody, string signatureHeader, string secret)
    {
        if (!signatureHeader.StartsWith("sha256="))
            return false;

        var receivedSig = signatureHeader["sha256=".Length..];
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
        var expectedSig = Convert.ToHexString(
            hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody))
        ).ToLowerInvariant();

        // Constant-time comparison to prevent timing attacks
        return CryptographicOperations.FixedTimeEquals(
            Convert.FromHexString(receivedSig),
            Convert.FromHexString(expectedSig)
        );
    }
}

Rejection Behavior

ConditionHTTP ResponseWorkflow
Missing signature header401 UnauthorizedNot started
Invalid signature401 UnauthorizedNot started
Valid signature200 OK (immediate mode)Started
Use the raw body for verification. Parse the JSON AFTER verifying the signature, and always sign/verify the raw bytes — not the parsed/re-serialized JSON. Key ordering differences in serialization will cause signature mismatches.