Validate Webhook Signatures
HMAC signatures apply to destinations registered through
POST /partner/webhook — the BNPL route.
Real-estate partner? Your destinations verify by token, not by signature, and none of the code on this page applies to you. Go to Verify the Webhook Token instead.
To ensure the security of your webhook integration, it's essential to validate that the webhook requests you receive are genuinely from banca.me and not from a malicious third party. This document explains how to validate webhook signatures.
Why Signature Validation Matters​
Without signature validation, attackers could potentially:
- Send fake webhooks to your system
- Trigger unwanted business logic
- Cause confusion or disrupt your operations
By validating signatures, you can ensure that only legitimate webhooks from banca.me are processed by your application.
How Signature Validation Works​
When banca.me sends a webhook to your endpoint, it includes a signature in the bancame-signature header. This signature is generated using your webhook secret (provided when you register the webhook) and the payload of the request.
The header has the format: t={timestamp},signature={signature}
To validate the signature:
- Extract the timestamp and signature from the
bancame-signatureheader - Create a string by concatenating the timestamp + '.' + the raw request body
- Generate an HMAC-SHA256 signature using your webhook secret as the key
- Compare the generated signature with the one in the header
If they match, the webhook is authentic and can be processed. If not, it should be rejected.
Implementation Example​
Here's an example of signature validation in Node.js:
const crypto = require("crypto");
function verifyWebhookSignature(payload, signatureHeader, secret) {
if (!signatureHeader) {
return false;
}
try {
// Parse the signature header
// Format: t={timestamp},signature={signature}
const [timestampPart, signaturePart] = signatureHeader.split(",");
if (!timestampPart || !signaturePart) {
return false;
}
const timestamp = timestampPart.split("=")[1];
const signature = signaturePart.split("=")[1];
if (!timestamp || !signature) {
return false;
}
// Create the string to sign (timestamp + '.' + JSON payload)
const payloadString = JSON.stringify(payload);
const stringToSign = `${timestamp}.${payloadString}`;
// Create the signature
const computedSignature = crypto
.createHmac("sha256", secret)
.update(stringToSign)
.digest("hex");
// Compare signatures using a timing-safe function to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(computedSignature)
);
} catch (error) {
console.error("Error verifying webhook signature:", error);
return false;
}
}
// Usage in Express.js
app.post("/webhooks/bancame", express.json(), (req, res) => {
const signatureHeader = req.headers["bancame-signature"];
const payload = req.body;
const secret = "your_webhook_secret"; // Store and retrieve securely
if (!verifyWebhookSignature(payload, signatureHeader, secret)) {
console.warn("Invalid webhook signature");
return res.status(400).send("Invalid signature");
}
// Signature is valid, process the webhook
res.status(200).send("Webhook received");
// Process in background
handleWebhook(payload);
});
Important Security Considerations​
-
Store your webhook secret securely: Never hardcode it in your application or commit it to version control.
-
Verify signatures before processing: Always verify the signature before processing any webhook data.
-
Use a constant-time comparison function: To prevent timing attacks, use a constant-time string comparison function like
crypto.timingSafeEqual(). -
Check timestamp freshness: Consider adding a check to ensure the webhook was sent recently:
// Check if the webhook was sent within the last 5 minutes
const MAX_TIMESTAMP_DIFF = 5 * 60 * 1000; // 5 minutes in milliseconds
const timestampDate = new Date(timestamp);
const now = new Date();
const diff = Math.abs(now - timestampDate);
if (diff > MAX_TIMESTAMP_DIFF) {
return false; // Webhook is too old
}
- Handle parsing errors gracefully: If the signature header format is invalid, reject the webhook.
Common Issues​
Invalid Signature Errors​
If you're receiving invalid signature errors, check:
- Are you using the correct webhook secret?
- Are you concatenating the timestamp and payload correctly?
- Are you parsing the JSON payload exactly as received (no modifications)?
- Are any middleware components modifying the request body?
Middleware Considerations​
Many web frameworks automatically parse JSON bodies, which can interfere with signature validation. For frameworks like Express.js, you might need to use the raw body:
// For Express.js, use the raw body for signature validation
app.post(
"/webhooks/bancame",
express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString();
},
}),
(req, res) => {
const signatureHeader = req.headers["bancame-signature"];
// Use the raw body for signature verification
const computedSignature = crypto
.createHmac("sha256", secret)
.update(timestamp + '.' + req.rawBody)
.digest("hex");
// Rest of the validation logic...
}
);
Next Steps​
After ensuring that your webhook validation is working correctly, take a look at our Best Practices to ensure your webhook implementation is reliable and robust.
For the payload that arrives inside the envelope, see
loan_approved in the Event Catalog.