Appearance
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
- When your webhook is registered, a shared secret is generated
- Each delivery includes an
X-Webhook-Signatureheader - The signature is computed as
sha256=HMAC-SHA256(secret, raw_body) - 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}), 200C#
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.timingSafeEqualin Node.js,hmac.compare_digestin Python,CryptographicOperations.FixedTimeEqualsin 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.

