DocsWebhooksSignature Verification

Webhook Signature Verification

Verify webhook signatures to ensure payloads are authentic and haven't been tampered with.

How Signature Verification Works
Step 1

We Sign the Payload

Using your webhook secret, we compute an HMAC-SHA256 hash of the raw request body.

Step 2

Signature in Header

The signature is sent in the X-Regen-Signature header.

Step 3

You Verify

Compute the same hash on your end and compare. If they match, the payload is authentic.

Webhook Headers
Headers included with every webhook request
HeaderDescriptionExample
X-Regen-SignatureHMAC-SHA256 signaturesha256=a1b2c3d4...
X-Regen-TimestampUnix timestamp when sent1703001234
X-Regen-EventEvent typeorder.created
X-Regen-Delivery-IDUnique delivery identifierdel_abc123...
X-Regen-Retry-CountNumber of retry attempts0
Verification Code Examples
Copy-paste ready code for verifying webhook signatures in your language
import crypto from 'crypto';

const WEBHOOK_SECRET = process.env.REGEN_WEBHOOK_SECRET;

export function verifyWebhookSignature(
  payload: string,
  signature: string,
  timestamp: string
): boolean {
  // Check timestamp is within 5 minutes (prevent replay attacks)
  const currentTime = Math.floor(Date.now() / 1000);
  const webhookTime = parseInt(timestamp, 10);
  
  if (Math.abs(currentTime - webhookTime) > 300) {
    console.error('Webhook timestamp too old');
    return false;
  }
  
  // Create the signed payload string
  const signedPayload = `${timestamp}.${payload}`;
  
  // Compute expected signature
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(signedPayload)
    .digest('hex');
  
  // Constant-time comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Usage in your webhook handler
export async function POST(request: Request) {
  const payload = await request.text();
  const signature = request.headers.get('X-Regen-Signature');
  const timestamp = request.headers.get('X-Regen-Timestamp');
  
  if (!verifyWebhookSignature(payload, signature, timestamp)) {
    return new Response('Invalid signature', { status: 401 });
  }
  
  const event = JSON.parse(payload);
  // Process the verified event...
  
  return new Response('OK', { status: 200 });
}
Timestamp Validation (Replay Attack Prevention)

The X-Regen-Timestamp header contains the Unix timestamp of when the webhook was sent. Always verify this timestamp to prevent replay attacks.

Recommended tolerance:

  • 5 minutes (300 seconds) - Recommended default
  • 1 minute (60 seconds) - High security environments
  • 15 minutes (900 seconds) - If your server clock may drift
Troubleshooting Signature Failures

Signature mismatch

Ensure you're using the raw request body, not a parsed/modified version. JSON parsing can alter whitespace and key ordering.

Wrong webhook secret

Each webhook endpoint has its own secret. Make sure you're using the correct secret for the specific endpoint receiving the webhook.

Encoding issues

Ensure the payload is treated as UTF-8. Different encodings will produce different hash values.

Debug tip

Log both the expected and received signatures (without exposing the secret) to identify where the mismatch occurs.