Guides
Guides
Guides

Webhooks

Signature Validation Guide

Introduction

To ensure the security and integrity of the data we send to your applications via webhooks, we implement signature verification. This document provides a step-by-step guide on how you can validate these webhook signatures using a secret key that we provide.

Requesting a Secret Key

To start receiving and validating webhook notifications, you first need a unique secret key:

  1. Request a Secret Key:
    1. If you have access to our Backoffice System click in your Avatar Icon and go to -> Profile Page
      1. There you can see your Webhook Secret.
    2. Contact our support team or use your customer dashboard (if available) to request a secret key for webhook verification.
    3. Update your secret by making a request to POST /v1/integrators/{integratorId}/update-webhook-secret. You will receive a 200 if successful, then you can view the secret in the Backoffice as listed in step i.
  2. Receive Your Key Securely: We will provide you with a unique secret key through a secure channel. This key is unique to your application and should not be shared.

Understanding Webhook Notifications Structure

Payload Produced Example

{"operation":"BENEFICIARY_KYC","resourceId":"27412213-a551-48f8-a9d6-61eab2f2bd6c","createdAt":[2024,3,15,19,23,59,401139000],"integratorId":"65f0a48a79ae4e2dd70e72e2","beneficiaryId":"65f0a49179ae4e2dd70e72e4","message":null,"success":null,"data":{"id":"d356b30e-dae5-49c7-b776-d28589fc6b92","simulationId":"8a80fa558e33f7c7018e3403f9600000","integratorId":"65f0a48a79ae4e2dd70e72e2","beneficiaryId":"65f0a49179ae4e2dd70e72e4","from":{"currencyCode":"BRL","value":402.9},"to":{"currencyCode":"USDC","value":79.29},"type":"ON_RAMP","status":"SUCCEEDED","marketExchangeRate":4.9603,"exchangeRate":4.9603,"fees":{"currencyCode":"BRL","value":8.058},"finalTransactionHash":null,"effectiveTransactionValue":5.0808}}

Integrator Callback Request Attributes

Property NameTypeDescription
operationIntegratorOperationThe type of operation that the callback is related to. This is an enumeration of possible operations.
resourceIdStringA unique identifier for the resource that is relevant to the callback event.
createdAtLocalDateTimeThe date and time when the event that triggered the callback occurred. Serialized using LocalDateTimeSerializer.
integratorIdStringThe unique identifier for the integrator. This helps in identifying which integrator the callback is associated with.
beneficiaryIdStringThe unique identifier for the beneficiary related to the callback event.
messageStringA human-readable message providing additional information about the callback event.
successBooleanIndicates whether the operation was successful (true) or not (false).
dataT (Generic Type)Additional data related to the callback event. The type of this data is generic and can vary.

IntegratorOperation Enumerator

The IntegratorOperation enumeration within IntegratorCallbackRequest defines the types of operations that can trigger a callback:

  • ON_RAMP: Represents an on-ramp operation.
  • OFF_RAMP: Represents an off-ramp operation.
  • PAYMENT_IN: Represents a payment-in operation.
  • PAYMENT_OUT: Represents a payment-out operation.
  • BENEFICIARY_BANK_ACCOUNT_VALIDATION: Represents a beneficiary bank account validation operation.
  • BENEFICIARY_KYC: Represents a Know Your Customer (KYC) operation for a beneficiary.
  • BENEFICIARY_KYB: Represents a Know Your Business (KYB) operation for a beneficiary.
  • BENEFICIARY_APPROVED: Represents an operation indicating the approval of a beneficiary.

Verifying Our Webhooks Via Signature

We generate our webhook signatures based in a random secret key provided to our integrators and the request payload above.

Signature Example

Signature example based in the payload above:

AbyU13J826tKxR2G5KWy8X46agiqnxaGuNaFjcf5bRI=

Verifying the Signature in Your Application

  1. Extract the Signature: Retrieve the signature from the X-Caliza-Webhook-Signature header of the incoming request.
  2. Get the Payload: Read the payload of the webhook.
    1. IMPORTANT: The payload must to be the exact and unmodified request body.
  3. Call verifySignature: Use the extracted signature, payload, and your secret key to validate the signature.

Implementing Signature Verification

Java Example

Use the following Java method to verify the signature of incoming webhook requests:


import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class WebhookSignatureVerifier {

    /**
     * Verifies a HMAC SHA-256 signature for a given payload using a secret key.
     * This method compares the received signature with a freshly computed one to validate webhook data integrity and authenticity.
     *
     * @param payload           The payload for which the signature was generated, the body of the webhook request.
     * @param receivedSignature The signature received in the webhook request, usually found in a request header.
     * @param secret            The secret key provided by us for each integrator - the same used to generate the signature.
     * @return true if the received signature matches the computed signature, false otherwise.
     * @throws NoSuchAlgorithmException If the SHA-256 hashing algorithm is not available.
     * @throws InvalidKeyException      If the given secret key is invalid.
     */
    public boolean verifySignature(HttpServletRequest httpRequest, String secret) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
        String payload = IOUtils.toString(httpRequest.getReader());
        String receivedSignature = httpRequest.getHeader("X-Caliza-Webhook-Signature");

        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
        sha256_HMAC.init(secretKey);

        byte[] hash = sha256_HMAC.doFinal(payload.getBytes());
        String calculatedSignature = Base64.getEncoder().encodeToString(hash);

        return MessageDigest.isEqual(calculatedSignature.getBytes(), receivedSignature.getBytes());
    }
}

When you receive a webhook, call verifySignature with the payload, the signature you received in the webhook header, and your secret key.

JavaScript Implementation using Node.js - using native http module


const http = require('http');
const crypto = require('crypto')

const secret = "my_webhook_secret";


//Extract the raw body
const server = http.createServer((req, res) => {
    const { headers, method, url } = req;
    if (req.url === '/your-endpoint' && req.method === 'POST') {
        // Listen for data events to collect the chunks of data.
        req.on('data', chunk => {
            //Get the raw body
            req.rawBody = chunk.toString();
        });

        req.on('end', () => {
            // verify signature
            var result = verifySignature(req, secret);
            // build http response
            res.statusCode = 200;
            res.setHeader('Content-Type', 'application/json');
            const responseBody = { headers, method, url, result };
            res.write(JSON.stringify(responseBody));
            res.end();
        });

    } else {
        res.writeHead(404);
        res.end();
    }
});;

/**
 * Validates the integrity of a webhook request by comparing the received signature with a calculated signature.
 * The function extracts the signature from the 'x-caliza-webhook-signature' HTTP header of the incoming request,
 * then generates a new signature using the request's raw body and a secret key. It uses the HMAC (Hash-Based Message Authentication Code)
 * algorithm with SHA-256 as the hash function to compute the signature. The calculated signature is then compared
 * to the received signature to verify the request's integrity. This method is essential for ensuring that the webhook
 * request is from a trusted source and has not been tampered with during transmission.
 */
function verifySignature(request, secret) {
    //Extract Signature header
    let receivedSignature = request.headers['x-caliza-webhook-signature'];
    let hmac = crypto.createHmac('sha256', secret);
    hmac.update(request.rawBody);
    let calculatedSignature = hmac.digest('base64');

    return calculatedSignature === receivedSignature;
}

server.listen(3000, () => {
    console.log('Server running on http://localhost:3000');
});

JavaScript Implementation using Express.js

const express = require('express');
const bodyParser = require("body-parser");
const crypto = require("crypto");

const app = express();

const secret = "my_webhook_secret";

//Extract the raw body
app.use(
    bodyParser.json({
      verify: (req, res, buf, encoding) => {
        if (buf && buf.length) {
          req.rawBody = buf.toString(encoding || "utf8");
        }
      },
    }),
);

app.post('/your-endpoint', (req, res) => {
  var result = verifySignature(req, secret);
  res.send(result);
});

/**
 * Validates the integrity of a webhook request by comparing the received signature with a calculated signature.
 * The function extracts the signature from the 'x-caliza-webhook-signature' HTTP header of the incoming request,
 * then generates a new signature using the request's raw body and a secret key. It uses the HMAC (Hash-Based Message Authentication Code)
 * algorithm with SHA-256 as the hash function to compute the signature. The calculated signature is then compared
 * to the received signature to verify the request's integrity. This method is essential for ensuring that the webhook
 * request is from a trusted source and has not been tampered with during transmission.
 */
function verifySignature(request, secret) {
  //Extract Signature header
  let receivedSignature = request.headers['x-caliza-webhook-signature'];
  let hmac = crypto.createHmac('sha256', secret);
  hmac.update(request.rawBody);
  let calculatedSignature = hmac.digest('base64');

  return calculatedSignature === receivedSignature;
}

app.listen(3001, () => {
  console.log('Server running on http://localhost:3000');
});

module.exports = app;

Best Practices for Managing Your Secret Key

  • Secure Storage: Store your secret key securely. Avoid hardcoding it in your application code or storing it in plain text files.
  • Limit Access: Only personnel who need to configure webhook processing should have access to the secret key.
  • Regular Rotation: Regularly request a new secret key to minimize the risk of unauthorized access.
  • Monitor Usage: Monitor the usage of your webhooks and be alert to any unusual patterns or failed validation attempts.

Need Help?

If you encounter any issues or need further assistance, please contact our support team.