Webhook Security
Every webhook request includes a signature so you can verify it originated from Stable Genius and wasn’t tampered with in transit.
Each webhook includes a X-StableGenius-Signature header containing an HMAC-SHA256 signature:
X-StableGenius-Signature: sha256=a1b2c3d4e5f6...
X-StableGenius-Timestamp: 1711929612
Verification Steps
Extract headers
Read the X-StableGenius-Signature and X-StableGenius-Timestamp headers from the request.
Prepare the signed payload
Concatenate the timestamp and the raw request body with a period: {timestamp}.{body}
Compute the expected signature
HMAC-SHA256 the signed payload using your webhook signing secret (found in the dashboard under Settings → Webhooks).
Compare signatures
Compare your computed signature with the one in the header. Use a constant-time comparison to prevent timing attacks.
Check timestamp freshness
Reject events with timestamps older than 5 minutes to prevent replay attacks.
Implementation Examples
const crypto = require('crypto');
function verifyWebhookSignature(req, signingSecret) {
const signature = req.headers['x-stablegenius-signature'];
const timestamp = req.headers['x-stablegenius-timestamp'];
const body = req.rawBody; // Raw request body as string
// Check timestamp freshness (5 minute tolerance)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
throw new Error('Webhook timestamp too old');
}
// Compute expected signature
const signedPayload = `${timestamp}.${body}`;
const expected = 'sha256=' + crypto
.createHmac('sha256', signingSecret)
.update(signedPayload)
.digest('hex');
// Constant-time comparison
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
throw new Error('Invalid webhook signature');
}
return true;
}
Signing Secret
Your webhook signing secret is available in the dashboard under Settings → Webhooks. Each webhook endpoint has its own signing secret.
Treat your webhook signing secret like an API key. Don’t commit it to source control. Store it in environment variables or a secrets manager.
Replay Protection
The timestamp header prevents replay attacks. If an attacker intercepts a webhook payload and resends it later, the timestamp check will reject it. Always verify that the timestamp is within 5 minutes of the current time.
IP Allowlisting
For additional security, you can restrict your webhook endpoint to only accept requests from Stable Genius IP addresses. Contact us for the current list of egress IPs.