# 🔐 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.
