Webhook Signatures
Verify webhook payloads are from Rhumby
Verifying Webhook Signatures
Every webhook request includes an HMAC-SHA256 signature in the X-Rhumby-Signature header. Always verify this signature before processing the payload.
How verification works
When you create a webhook, Rhumby generates a secret key. We use this secret to sign every payload with HMAC-SHA256. You verify the signature using the same secret.
Node.js example
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware
app.post('/webhooks/rhumby', (req, res) => {
const signature = req.headers['x-rhumby-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhook(payload, signature, process.env.RHUMBY_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
switch (event.type) {
case 'results.published':
handleResultsPublished(event.data);
break;
case 'registration.created':
handleNewRegistration(event.data);
break;
}
res.status(200).send('OK');
});Python example
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Security best practices
- Always verify signatures before processing payloads
- Use
timingSafeEqual(Node.js) orcompare_digest(Python) to prevent timing attacks - Store your webhook secret in environment variables, not in code
- Return 200 quickly, then process the event asynchronously
- Log failed verifications for monitoring