Signing Secrets
Signing secrets let you verify that webhook requests forwarded by Hooklistener are authentic and haven't been tampered with. Hooklistener uses HMAC-SHA256 to sign forwarded requests.
How it works
- You generate a signing secret for your organization
- When Hooklistener forwards a request, it includes two headers:
X-Webhook-Signature— the HMAC-SHA256 signatureX-Webhook-Timestamp— the Unix timestamp when the signature was generated
- Your server reconstructs the signed payload (
{timestamp}.{body}) and verifies the signature
Generating a signing secret
Dashboard
Go to Organization Settings and generate a signing secret.
API
curl -X POST https://app.hooklistener.com/api/v1/organizations/<org-id>/signing_secret \
-H "Authorization: Bearer hklst_your_api_key"
Rotating secrets
If a secret is compromised, rotate it immediately:
curl -X POST https://app.hooklistener.com/api/v1/organizations/<org-id>/signing_secret/roll \
-H "Authorization: Bearer hklst_your_api_key"
The old secret is invalidated immediately. Update your verification code with the new secret.
Verifying signatures
Node.js
const crypto = require('crypto');
function verifyWebhook(body, signatureHeader, timestampHeader, secret) {
const payload = `${timestampHeader}.${body}`;
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('base64');
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected)
);
}
// Usage in Express:
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
if (verifyWebhook(req.body, signature, timestamp, process.env.SIGNING_SECRET)) {
// Valid signature — process the webhook
}
});
Python
import hmac
import hashlib
import base64
def verify_webhook(body: str, signature: str, timestamp: str, secret: str) -> bool:
payload = f"{timestamp}.{body}"
expected = base64.b64encode(
hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).digest()
).decode()
return hmac.compare_digest(signature, expected)
Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
)
func verifyWebhook(body, signature, timestamp, secret string) bool {
payload := fmt.Sprintf("%s.%s", timestamp, body)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
expected := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expected))
}
Security considerations
- Store signing secrets securely (use environment variables or a secrets manager)
- Always use constant-time comparison to prevent timing attacks
- Rotate secrets periodically and immediately after any suspected compromise
- Verify signatures before processing any forwarded webhook data