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
- Extract the
X-Maia-Signatureheader from the request - Get the raw JSON payload body
- Compute HMAC-SHA256 of the payload using your webhook secret
- 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:
- Navigate to Channels in the MAIA dashboard
- Select the channel
- View the webhook configuration section
- 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
- Always verify signatures - Never process webhooks without verification
- Use constant-time comparison - Prevents timing attacks
- Use HTTPS only - Webhook URLs must be HTTPS
- Rotate secrets periodically - Update your webhook secret regularly
- Log verification failures - Monitor for potential attacks
- Reject replayed requests - Optionally check
timestampto reject old webhooks
Troubleshooting
Signature Mismatch
Common causes:
| Issue | Solution |
|---|---|
| Incorrect secret | Verify you're using the correct webhook secret from the channel |
| Body modification | Ensure you're using the raw request body, not parsed JSON |
| Encoding issues | Verify UTF-8 encoding is consistent |
| Whitespace | Don'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"