Webhook Security
Verify webhook signatures for security
Webhook Security
Verify webhook signatures to ensure requests are from APIMW.
Signature Verification
Every webhook request includes a signature header:
X-APIMW-Signature: sha256=abc123...
How to Verify
1. Get the Raw Request Body
$payload = file_get_contents('php://input');
2. Compute the Expected Signature
$secret = 'your_webhook_secret';
$expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
3. Compare Signatures
$receivedSignature = $_SERVER['HTTP_X_APIMW_SIGNATURE'];
if (hash_equals($expectedSignature, $receivedSignature)) {
// Valid webhook
} else {
// Invalid - reject
http_response_code(401);
exit;
}
Code Examples
PHP
function verifyWebhookSignature($payload, $signature, $secret) {
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_APIMW_SIGNATURE'] ?? '';
if (!verifyWebhookSignature($payload, $signature, $webhookSecret)) {
http_response_code(401);
die('Invalid signature');
}
$event = json_decode($payload, true);
// Process event...
Node.js
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post('/webhook', (req, res) => {
const signature = req.headers['x-apimw-signature'];
if (!verifySignature(req.rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process event...
res.status(200).send('OK');
});
Python
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected = 'sha256=' + hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-APIMW-Signature', '')
if not verify_signature(request.data.decode(), signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.json
# Process event...
return 'OK', 200
Security Best Practices
- Always Verify - Never skip signature verification
- Use Timing-Safe Comparison - Prevent timing attacks
- Keep Secrets Secure - Store in environment variables
- Use HTTPS - Only accept webhooks over HTTPS
- Reject Invalid Requests - Return 401 for bad signatures