Skip to content

Webhook Security

To ensure webhook requests are genuinely from MAIA and haven't been tampered with, every webhook includes an HMAC-SHA256 signature that you should verify.

Signature Header

Each webhook request includes the X-Maia-Signature header containing the HMAC-SHA256 signature of the payload.

X-Maia-Signature: a1b2c3d4e5f6...

Verification Process

  1. Extract the X-Maia-Signature header from the request
  2. Get the raw JSON payload body
  3. Compute HMAC-SHA256 of the payload using your webhook secret
  4. Compare the computed signature with the header value

Critical Security

Always verify the signature before processing webhook data. Never trust webhook payloads without verification—they could be spoofed by attackers.

Implementation Examples

n8n (Code Node)

javascript
const SECRET = '<SECRET-KEY-HERE>';

// 1. Generate HMAC
const computedSignature = require('crypto')
  .createHmac('sha256', SECRET)
  .update(JSON.stringify($json.body))
  .digest('hex');

// 2. Compare
if (computedSignature !== $json.headers['x-maia-signature']) {
  throw new Error('Invalid webhook signature');
}

return [
  {
    json: $json.body,
  },
];

Node.js

javascript
const crypto = require('crypto');
const express = require('express');

const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// Important: Use raw body for signature verification
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-maia-signature'];
  const payload = req.body;

  // Compute expected signature
  const expectedSignature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  // Constant-time comparison to prevent timing attacks
  const isValid = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );

  if (!isValid) {
    console.error('Invalid webhook signature');
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Signature valid - process the webhook
  const data = JSON.parse(payload);
  console.log('Verified webhook:', data.messageId);

  res.status(200).json({ received: true });
});

app.listen(3000);

Getting Your Webhook Secret

Your webhook secret is generated automatically when you configure a webhook URL. You can find it:

  1. Navigate to Channels in the MAIA dashboard
  2. Select the channel
  3. View the webhook configuration section
  4. Copy the webhook secret

Keep It Secret

Never expose your webhook secret in client-side code, public repositories, or logs. Store it securely as an environment variable.

Security Best Practices

  1. Always verify signatures - Never process webhooks without verification
  2. Use constant-time comparison - Prevents timing attacks
  3. Use HTTPS only - Webhook URLs must be HTTPS
  4. Rotate secrets periodically - Update your webhook secret regularly
  5. Log verification failures - Monitor for potential attacks
  6. Reject replayed requests - Optionally check timestamp to reject old webhooks

Troubleshooting

Signature Mismatch

Common causes:

IssueSolution
Incorrect secretVerify you're using the correct webhook secret from the channel
Body modificationEnsure you're using the raw request body, not parsed JSON
Encoding issuesVerify UTF-8 encoding is consistent
WhitespaceDon't modify the payload (trim, format) before verification

Testing Signatures

You can test signature generation locally:

bash
# Generate a test signature
echo -n '{"test":"payload"}' | openssl dgst -sha256 -hmac "your-webhook-secret"

MAIA Platform Documentation