Mava Docs
  • 👋Introducing Mava
    • Book a demo
    • What is Mava?
  • 🟢Getting Started
    • Create a free account
    • Integration Setup
      • Web Chat Setup
        • Automatically capture custom user data (SDK)
        • Automatically open the web chat on page load
        • Create custom launch event
        • Hide web chat icon
        • Delay script load
        • Customize the position & Size of the Web chat
      • Discord Setup
        • Discord Ticket Setup
        • Discord Public Channel AI Setup
        • Changing the Discord Bot Name
        • Automatically add users or all users with a role into a Discord private thread
        • Responding to tickets within Discord
        • Discord Thread Limits & Archiving
      • Telegram Setup
        • Telegram Group Ticketing
      • Email Setup
        • Email Forwarding
        • Custom Email Domains
    • Inviting Team Members
      • Transferring account ownership
      • Existing Wallet User - How To Verify Your Email
    • Linking your Discord Profile to Mava
    • Chatbot Builder
    • Attributes
  • 🔄Ticket Automations
  • 🤖AI Support
    • AI Training
    • Private Support Ticket AI
    • Public Discord Channel AI
    • Public Telegram Group AI
    • Public Slack AI Channel
  • 🔗Webhooks & API
    • 🪝Webhooks
      • 📋Schemas
      • 🔐 Webhook Security Guide
      • 📩Deliverability
    • 💻API
    • No-Code Integrations
  • 📥The Support Inbox
    • Inbox Keyboard Shortcuts
    • Ticket Status
    • How to create a custom inbox view
    • How to copy a user's Telegram/Discord ID or email address
    • Tags
    • Changing your team's workspace name
  • 🎟️Ticket Behavior & Settings
    • How to open a ticket in Discord
    • Spam & Ticket limits
    • Auto-Resolve Tickets
    • Re-opening Resolved Tickets
    • Discord Threads & Limits
    • Discord Ticket Transcripts
  • 🔔Notifications
    • Personal Notifications
    • Team Notifications
  • 💲Price Plans
    • Pricing FAQs
  • 💸Affiliate Program
  • 📱Using Mava on Mobile
  • 🔐Data Protection & Security
  • ✅Going Live Checklist
  • ⚠️Workspace Account Deletion
Powered by GitBook
On this page
  • Overview
  • Implementation Guide
  • Processing a Webhook
  • Security Notes

Was this helpful?

  1. Webhooks & API
  2. Webhooks

🔐 Webhook Security Guide

Mava 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:

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:

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:

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.

PreviousSchemasNextDeliverability

Last updated 5 months ago

Was this helpful?

🔗
🪝