How to verify webhook requests using HMAC signatures.
Verification
Why Verify Webhooks?
Anyone who knows your webhook URL could send fake requests. Verification ensures the request actually came from Passkallet using HMAC-SHA256 signatures.
How It Works
- Passkallet builds the payload (event, timestamp, data) as JSON
- Passkallet signs the JSON string with your webhook secret using HMAC-SHA256
- Passkallet adds the signature to the payload body and the
X-Passkallet-Signatureheader - You extract the payload without the signature field, compute the same HMAC, and compare
| Header / Field | Description |
|---|---|
X-Passkallet-Signature | HMAC-SHA256 signature with sha256= prefix (in HTTP header) |
signature | Same signature included in the JSON body |
The signature is computed over the JSON payload without the
signature field. The User-Agent header is always Passkallet-Webhook/1.0.Verification Code
javascript
const crypto = require("crypto");
function verifyWebhookSignature(req, secret) {
const signatureHeader = req.headers["x-passkallet-signature"];
// Remove the signature field from the body before computing HMAC
const { signature: bodySig, ...payloadWithoutSig } = req.body;
const payloadString = JSON.stringify(payloadWithoutSig);
const expectedSignature =
"sha256=" +
crypto
.createHmac("sha256", secret)
.update(payloadString)
.digest("hex");
// Use constant-time comparison
const isValid = crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expectedSignature)
);
return isValid;
}Important Security Notes
- Always use
crypto.timingSafeEqualfor comparing signatures (prevents timing attacks) - Keep your webhook secret secure in environment variables
- The webhook secret is auto-generated when you configure the webhook -- you cannot set a custom one
- If you reconfigure the webhook, a new secret is generated (the old one stops working)