Complete guide to webhooks, debugging, and real-time event monitoring with HookMetry
HMAC SHA256 is the industry-standard method for securing webhooks. Use this for custom integrations or APIs that don't match Stripe/GitHub/Shopify formats.
HMAC (Hash-based Message Authentication Code) combines a secret key with your webhook payload to create a unique signature:
signature = HMAC-SHA256(secret_key, request_body)Hookmetry automatically checks these common header names:
X-SignatureX-Webhook-SignatureX-Hub-SignatureX-HMAC-Signatureconst crypto = require('crypto');
const axios = require('axios');
// SENDER SIDE: You are the one sending the webhook.
// You own both the payload and the secret.
const payload = JSON.stringify({ event: 'user.created', userId: 123 });
const secret = process.env.WEBHOOK_SECRET;
// Hash the serialized string
const signature = crypto
.createHmac('sha256', secret)
.update(payload) // ← string here is fine — YOU control the format
.digest('hex');
await axios.post('https://api.hookmetry.com/webhook/ep_YOUR_ENDPOINT_ID', payload, {
headers: {
'Content-Type': 'application/json',
'X-Signature': signature
}
});
// ⚠️ RECEIVER SIDE is different — see "Verifying" section below.import hmac, hashlib, json, os
import requests
# SENDER SIDE: serialize first, then hash the string
payload_str = json.dumps({'event': 'user.created', 'userId': 123})
secret = os.environ['WEBHOOK_SECRET'].encode('utf-8')
signature = hmac.new(secret, payload_str.encode('utf-8'), hashlib.sha256).hexdigest()
requests.post(
'https://api.hookmetry.com/webhook/ep_YOUR_ENDPOINT_ID',
data=payload_str,
headers={
'Content-Type': 'application/json',
'X-Signature': signature
}
)<?php
function generateHMAC($payload, $secret) {
$json = json_encode($payload);
$signature = hash_hmac('sha256', $json, $secret);
return $signature;
}
// Usage
$payload = ['event' => 'user.created', 'userId' => 123];
$secret = 'your_webhook_secret';
$signature = generateHMAC($payload, $secret);
// Send webhook
$ch = curl_init('https://api.hookmetry.com/webhook/ep_YOUR_ENDPOINT_ID');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Signature: ' . $signature
]);
curl_exec($ch);
?>The receiver side is fundamentally different: you must hash the raw request body bytes, not a re-serialized JSON object. Parsing and re-stringifying will change whitespace and field order, breaking the signature.
const crypto = require('crypto');
const express = require('express');
const app = express();
// CRITICAL: express.raw() — not express.json() — for webhook routes.
// express.json() parses the body and loses the raw bytes Hookmetry signed.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const receivedSig = req.headers['x-signature'];
if (!receivedSig) return res.status(400).send('Missing signature header');
// req.body is a raw Buffer — hash it directly
const expectedSig = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body) // ← raw Buffer, NOT JSON.stringify(req.body)
.digest('hex');
// Timing-safe comparison
const a = Buffer.from(receivedSig, 'hex');
const b = Buffer.from(expectedSig, 'hex');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Safe to parse now
const event = JSON.parse(req.body);
console.log('Valid event:', event.type);
res.status(200).json({ received: true });
});Security Best Practices:
Mismatched Payload Serialization
The sender and receiver must serialize the payload identically. Watch out for:
Using Raw Body vs Parsed JSON
If you parse JSON before verification, you may alter the payload. Always verify against the raw body.
Case Sensitivity Issues
HMAC signatures are case-sensitive. Don't convert to uppercase/lowercase.
Testing HMAC Webhooks:
Create a Custom HMAC endpoint in Hookmetry, set your secret, then use the code examples above to send test webhooks. Check the webhook logs to see validation results and debug signature mismatches.
Was this page helpful?
Your feedback helps us improve the docs.