Skip to main content

Validate Webhook Signatures

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:

  1. Extract the timestamp and signature from the bancame-signature header
  2. Create a string by concatenating the timestamp + '.' + the raw request body
  3. Generate an HMAC-SHA256 signature using your webhook secret as the key
  4. 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​

  1. Store your webhook secret securely: Never hardcode it in your application or commit it to version control.

  2. Verify signatures before processing: Always verify the signature before processing any webhook data.

  3. Use a constant-time comparison function: To prevent timing attacks, use a constant-time string comparison function like crypto.timingSafeEqual().

  4. 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
}
  1. 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:

  1. Are you using the correct webhook secret?
  2. Are you concatenating the timestamp and payload correctly?
  3. Are you parsing the JSON payload exactly as received (no modifications)?
  4. 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.