> ## Documentation Index
> Fetch the complete documentation index at: https://docs.finventi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Signature Verification

> How to verify the authenticity and integrity of webhook notifications

To ensure the authenticity and integrity of webhooks sent from our system, each webhook includes a digital signature. This signature allows you to verify that the payload has not been altered during transmission and confirms the webhook's origin.

## Overview

Each webhook includes the signature in the `finventi-signature-N` HTTP header, where `N` represents the version of the signature. The current version is `1` (`finventi-signature-1`). When public keys are rotated, new header versions are issued, ensuring backward compatibility for clients until their code is updated.

The signature is verified using the **RSASSA-PKCS1-v1.5** algorithm and the appropriate public key.

## HTTP Headers

Each webhook includes the following HTTP headers that must be used during the signature verification process:

<ParamField header="finventi-signature-N" type="string" required>
  The webhook signature (where N is the version number, currently `1`)
</ParamField>

<ParamField header="finventi-signature-timestamp" type="string" required>
  A UNIX timestamp (UTC) indicating when the webhook was sent
</ParamField>

<ParamField header="finventi-receiver-tenant-id" type="string" required>
  The tenant ID for the webhook recipient
</ParamField>

## Public Key for Webhook Verification

Use the following public keys to verify webhook signatures:

<Tabs>
  <Tab title="Sandbox">
    ***Version - 1 (current)***

    ```text theme={null}
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvoc7GrFbduCeSVxFPJ3l
    a0NRa0caUqBddQAOUxuHTOuShOvdKbxRYc5u1vb9YNLJWjx4XSHESp8Q7oocqXt8
    +weBFsk/kAtJ4zjbYPY1PvAOLe+WObdxxZtfwzpwVxbtP6GQk5aUi2HbITe3EDf/
    7WEmvnAcWm++Mo6+GSh2Ky1t6o4htrx1lH2gYVg0iRHx1W9lLXjMl/5oLi1C6dtx
    TnBmXMlN/NT5YYU4lVlXQBZzS7a8ZgwosfW+v1uCimzbGcWytmmcFISjSNqkYaeg
    IXDYwKLwlsWtm975ln6UL20KcSt7ia+Lpuv7cdxJlOY95y0ds/PCw1x0HEPxU+44
    swIDAQAB
    -----END PUBLIC KEY-----
    ```
  </Tab>

  <Tab title="Production">
    ***Version - 1 (current)***

    ```text theme={null}
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1pTRk8oUoGAAgXE8Gni8
    xvG2MJz6uYkZcNlXhPTrsxfqD4W5uDZOhy7APLz47Plcv+XL45kO0BFgjYHDRASx
    IIb6K5+YVyf9p1biLdZgf047wxwuC65bT6ddhuSX9FrMEtgPvghltSSfTZjdwVck
    QNE2JEV7vcIfq3ke5US+SP+6AHIcPyXypslp50CtO8wpZG+pz7rSdOoiRFTMhzVZ
    efDetRcnFv6DoAdapOiNVG9MBPqnE+BT5mnAlgF6V551ZJFOMhFvm/cLtP3Gj2a9
    /zNeCT1ZXHIuqxwrBUy/R8RSKJJIBWUbJEHF+pcPite+MgJm529Bt6K4mGCv1Nzv
    GwIDAQAB
    -----END PUBLIC KEY-----
    ```
  </Tab>
</Tabs>

## Signature Verification Process

To verify the authenticity of the webhook, follow these steps:

<Steps>
  <Step title="Concatenate the verification data">
    Concatenate the following components in the specified order, separated by periods (`.`):

    * The request body (raw JSON)
    * The tenant ID from `finventi-receiver-tenant-id` header
    * The timestamp from `finventi-signature-timestamp` header

    Example:

    ```text theme={null}
    {"trx_id":10300003,"end_to_end_id":"NOTPROVIDED","type":"Payment","direction":"OUTBOUND","amount":1,"currency":"EUR","status":"Created","updated_at":"2024-09-20T13:46:32.092083Z"}.demo1.1726839992
    ```
  </Step>

  <Step title="Hash the concatenated string">
    Hash the concatenated string using the **SHA-256** algorithm.
  </Step>

  <Step title="Decode the signature">
    Base64 decode the received `finventi-signature-1` header to obtain the signature bytes.
  </Step>

  <Step title="Verify the signature">
    Verify the signature using [RSASSA-PKCS1-v1\_5](https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2) with:

    * The hashed data from step 2
    * The decoded signature from step 3
    * The public key provided above
  </Step>
</Steps>

## Code Example (Node.js)

Here's a complete example of webhook signature verification in Node.js:

```javascript theme={null}
const crypto = require('crypto');

function verifyWebhookSignature(body, headers) {
  // Extract headers
  const signatureBase64 = headers['finventi-signature-1'];
  const tenantId = headers['finventi-receiver-tenant-id'];
  const timestamp = headers['finventi-signature-timestamp'];
  
  // Public key for sandbox environment
  const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvoc7GrFbduCeSVxFPJ3l
a0NRa0caUqBddQAOUxuHTOuShOvdKbxRYc5u1vb9YNLJWjx4XSHESp8Q7oocqXt8
+weBFsk/kAtJ4zjbYPY1PvAOLe+WObdxxZtfwzpwVxbtP6GQk5aUi2HbITe3EDf/
7WEmvnAcWm++Mo6+GSh2Ky1t6o4htrx1lH2gYVg0iRHx1W9lLXjMl/5oLi1C6dtx
TnBmXMlN/NT5YYU4lVlXQBZzS7a8ZgwosfW+v1uCimzbGcWytmmcFISjSNqkYaeg
IXDYwKLwlsWtm975ln6UL20KcSt7ia+Lpuv7cdxJlOY95y0ds/PCw1x0HEPxU+44
swIDAQAB
-----END PUBLIC KEY-----`;

  // Step 1: Concatenate the data
  const dataToVerify = Buffer.from(body + '.' + tenantId + '.' + timestamp);
  
  // Step 2: Hash is done internally by crypto.verify
  
  // Step 3: Decode the signature
  const signature = Buffer.from(signatureBase64, 'base64');
  
  // Step 4: Verify the signature
  const isVerified = crypto.verify(
    "sha256",
    dataToVerify,
    {
      key: publicKey,
      padding: crypto.constants.RSA_PKCS1_PADDING,
    },
    signature
  );
  
  return isVerified;
}

// Example usage
const webhookBody = '{"trx_id":10300003,"end_to_end_id":"NOTPROVIDED","type":"Payment","direction":"OUTBOUND","amount":1,"currency":"EUR","status":"Created","updated_at":"2024-09-20T13:46:32.092083Z"}';
const webhookHeaders = {
  'finventi-signature-1': 'GtZFu1uNFqOir8eDkar7+d/S+FtwQpk4mPGCuByKhJG29K1u7ynbVhkrDF8c3TqyX9wYHxpOa94FsgW2I4CnLh+B24LqL7WVSuACOL6GoSjfKeXP00NSp0ps8QYbVaJ8Ys6E4FePhp+7piAACkIP5vZ91JCLQ8lz36KRJlOnByQMTBH6j924n1GwZiZfbMojOGmMhLA0h8jWgTIeuvYPswiZXZXp0vpqJfWmdoqiU1ldoausTNFyoVwFuuzkxPv1VHvWeEeWirObUv3wNpyAnLnqomDgR7pe/9dDV0bkq5r0JkRhnbPCEFE/zzHeDgZ957hv8Oq3wJkGJarZ0NvPLw==',
  'finventi-receiver-tenant-id': 'demo1',
  'finventi-signature-timestamp': '1726839992'
};

const isValid = verifyWebhookSignature(webhookBody, webhookHeaders);
console.log("Verification successful:", isValid);
```

## Express.js Middleware Example

Here's how to implement webhook verification as Express.js middleware:

```javascript theme={null}
const express = require('express');
const crypto = require('crypto');

function webhookVerificationMiddleware(req, res, next) {
  // Get raw body
  const rawBody = req.rawBody || JSON.stringify(req.body);
  
  // Extract headers
  const signatureBase64 = req.headers['finventi-signature-1'];
  const tenantId = req.headers['finventi-receiver-tenant-id'];
  const timestamp = req.headers['finventi-signature-timestamp'];
  
  if (!signatureBase64 || !tenantId || !timestamp) {
    return res.status(401).json({ error: 'Missing required webhook headers' });
  }
  
  // Verify signature
  const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvoc7GrFbduCeSVxFPJ3l
a0NRa0caUqBddQAOUxuHTOuShOvdKbxRYc5u1vb9YNLJWjx4XSHESp8Q7oocqXt8
+weBFsk/kAtJ4zjbYPY1PvAOLe+WObdxxZtfwzpwVxbtP6GQk5aUi2HbITe3EDf/
7WEmvnAcWm++Mo6+GSh2Ky1t6o4htrx1lH2gYVg0iRHx1W9lLXjMl/5oLi1C6dtx
TnBmXMlN/NT5YYU4lVlXQBZzS7a8ZgwosfW+v1uCimzbGcWytmmcFISjSNqkYaeg
IXDYwKLwlsWtm975ln6UL20KcSt7ia+Lpuv7cdxJlOY95y0ds/PCw1x0HEPxU+44
swIDAQAB
-----END PUBLIC KEY-----`;
  
  const dataToVerify = Buffer.from(rawBody + '.' + tenantId + '.' + timestamp);
  const signature = Buffer.from(signatureBase64, 'base64');
  
  const isVerified = crypto.verify(
    "sha256",
    dataToVerify,
    {
      key: publicKey,
      padding: crypto.constants.RSA_PKCS1_PADDING,
    },
    signature
  );
  
  if (!isVerified) {
    return res.status(401).json({ error: 'Invalid webhook signature' });
  }
  
  next();
}

// Usage
const app = express();

// Important: Capture raw body for signature verification
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString('utf8');
  }
}));

app.post('/webhook/payment-status', webhookVerificationMiddleware, (req, res) => {
  // Handle verified webhook
  console.log('Verified webhook received:', req.body);
  res.status(200).send('OK');
});
```

## Security Best Practices

<Warning>
  Always verify webhook signatures to ensure the authenticity of incoming webhooks. Never process webhook data without proper verification.
</Warning>

1. **Store the public key securely** - Keep the public key in your configuration management system
2. **Verify timestamps** - Check that the timestamp is recent to prevent replay attacks
3. **Use HTTPS** - Always expose your webhook endpoints over HTTPS
4. **Log verification failures** - Monitor and log all signature verification failures
5. **Handle version rotation** - Be prepared to support multiple signature versions during key rotation periods
