# 🔐 Webhook Security Guide

## Overview

Mava webhooks use a two-layer encryption system to ensure maximum security:

1\. RSA encryption for key exchange

2\. AES encryption for payload data

This dual-layer approach provides both security and performance, allowing us to safely handle large payloads while maintaining end-to-end encryption.

#### Key Components

* **Signing Key**: A private key provided to you in the UI (prefixed with 'mava\_wh\_')
* **Encryption Key**: A public key used to encrypt the symmetric key
* **Symmetric Key**: A unique AES key generated for each webhook event
* **IV**: A random initialization vector used for AES encryption

### Implementation Guide

1. **Verifying Webhook Authenticity**

Each webhook includes a signature that you should verify before processing the payload:

```typescript
async function verifyEventSignature(
  encryptedEvent: string,
  signature: string,
  encryptedSymmetricKey: string,
  signingKey: string
) {
  try {
    // Split the encrypted key into IV and symmetric key components
    const [iv, symmetricKey] = encryptedSymmetricKey.split(':');
    // Extract the private key (removing mava_wh_ prefix)
    const key = signingKey.split('_')[2];
    const privateKeyBuffer = Buffer.from(key, 'base64');
    // Decrypt the symmetric key using RSA with OAEP padding
    const decryptedSymmetricKey = crypto.privateDecrypt(
      {
        key: privateKeyBuffer,
        format: 'der',
        type: 'pkcs8',
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
      },
      Buffer.from(symmetricKey, 'base64')
    );
    // Create HMAC using the decrypted symmetric key
    const hmac = crypto.createHmac('sha256', decryptedSymmetricKey.toString('base64'));
    hmac.update(encryptedEvent);
    const regeneratedSignature = hmac.digest('hex');
    // Compare signatures using a timing-safe comparison
    return crypto.timingSafeEqual(
      Buffer.from(regeneratedSignature, 'hex'),
      Buffer.from(signature, 'hex')
    );
  } catch (err) {
    throw new Error('Failed to verify event signature');
  }
}
```

#### 2. Decrypting the Payload

After verifying the signature, decrypt the payload using this process:

```typescript
async function decryptPayload(
  encryptedPayload: string,
  encryptedSymmetricKey: string,
  signingKey: string
) {
  try {
    // Split the encrypted key into IV and symmetric key components
    const [iv, symmetricKey] = encryptedSymmetricKey.split(':');
    // Extract the private key (removing mava_wh_ prefix)
    const key = signingKey.split('_')[2];
    const privateKeyBuffer = Buffer.from(key, 'base64');
    // Decrypt the symmetric key using RSA with OAEP padding
    const decryptedSymmetricKey = crypto.privateDecrypt(
      {
        key: privateKeyBuffer,
        format: 'der',
        type: 'pkcs8',
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
      },
      Buffer.from(symmetricKey, 'base64')
    );
    // Decrypt the payload using AES-256-CBC
    const decipher = crypto.createDecipheriv(
      'aes-256-cbc',
      decryptedSymmetricKey,
      Buffer.from(iv, 'base64')
    );
    let decrypted = decipher.update(Buffer.from(encryptedPayload, 'base64'));
    decrypted = Buffer.concat([decrypted, decipher.final()]);
    return decrypted.toString('utf8');
  } catch (err) {
    throw new Error('Failed to decrypt payload');
  }
}
```

### Processing a Webhook

When you receive a webhook, you'll get:

* `payload`: The encrypted event data
* `key`: The encrypted symmetric key with IV (format: `iv:encryptedKey`)
* `signature`: The HMAC signature for verification
* `webhookId`: A unique identifier for the webhook

Example webhook processing:

```typescript
app.post('/webhook', async (req, res) => {
  const { payload, key, signature, webhookId } = req.body;
  const signingKey = process.env.MAVA_SIGNING_KEY; // Your signing key from the UI
  try {
    // 1. Verify the signature
    const isValid = await verifyEventSignature(payload, signature, key, signingKey);
    if (!isValid) {
      return res.status(401).send('Invalid signature');
    }
    // 2. Decrypt the payload
    const decryptedPayload = await decryptPayload(payload, key, signingKey);
    const eventData = JSON.parse(decryptedPayload);
    // 3. Process the event
    await processEvent(eventData);
    res.status(200).send('OK');
  } catch (err) {
    res.status(400).send('Failed to process webhook');
  }
});
```

### Security Notes

* Store your signing key securely and never expose it publicly.
* Always verify the signature before processing the payload.
* Use timing-safe comparison for signature verification.
* The encryption uses RSA-OAEP for key exchange and AES-256-CBC for payload encryption.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mava.gitbook.io/mava-docs/webhooks-and-api/webhooks/webhook-security-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
