const crypto = require('crypto');
function validateWebhookSignature(req, secretKey) {
try {
// Get signature header
const signatureHeader = req.headers['signature'];
if (!signatureHeader) {
throw new Error('Missing signature header');
}
// Parse signature components
const parts = signatureHeader.split(',');
const timestampPart = parts.find(part => part.startsWith('t='));
const signaturePart = parts.find(part => part.startsWith('v1='));
if (!timestampPart || !signaturePart) {
throw new Error('Invalid signature format');
}
const timestamp = timestampPart.split('=')[1];
const signature = signaturePart.split('=')[1];
// Validate timestamp (prevent replay attacks)
// PUBLISHER RESPONSIBILITY: Adjust tolerance based on your needs
const currentTime = Date.now();
const requestTime = parseInt(timestamp);
const timeDiff = Math.abs(currentTime - requestTime);
const maxAge = 5 * 60 * 1000; // 5 minutes - ADJUST AS NEEDED
if (timeDiff > maxAge) {
throw new Error('The request is expired - possible replay attack.');
}
// Reconstruct expected signature
const payloadToSign = `${timestamp}.${JSON.stringify(req.body)}`;
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(payloadToSign)
.digest('hex');
// Compare signatures
if (signature !== expectedSignature) {
throw new Error('Invalid signature');
}
// Get project ID
const projectId = req.headers['x-project-id'];
console.log(`Valid webhook from project: ${projectId}`);
return true;
} catch (error) {
console.error('Webhook validation failed:', error.message);
return false;
}
}
// Usage example
app.post('/webhook', (req, res) => {
// PUBLISHER RESPONSIBILITY: Always validate before processing
const isValid = validateWebhookSignature(req, 'your-secret-key');
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Only process webhook after successful validation
console.log('Processing webhook:', req.body);
res.status(200).json({ status: 'ok' });
});