Skip to main content

Webhook Security

Every webhook request includes a signature so you can verify it originated from Stable Genius and wasn’t tampered with in transit.

Signature Header

Each webhook includes a X-StableGenius-Signature header containing an HMAC-SHA256 signature:
X-StableGenius-Signature: sha256=a1b2c3d4e5f6...
X-StableGenius-Timestamp: 1711929612

Verification Steps

1

Extract headers

Read the X-StableGenius-Signature and X-StableGenius-Timestamp headers from the request.
2

Prepare the signed payload

Concatenate the timestamp and the raw request body with a period: {timestamp}.{body}
3

Compute the expected signature

HMAC-SHA256 the signed payload using your webhook signing secret (found in the dashboard under Settings → Webhooks).
4

Compare signatures

Compare your computed signature with the one in the header. Use a constant-time comparison to prevent timing attacks.
5

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.