Webhook Signing and Verification

Authenticate AuthOS webhook deliveries and limit replay attacks

AuthOS release 0.8.2 API v1 Latest-only documentation
Updated Jul 15, 2026 Edit this page
On this page

Security

Signature Verification

The implementation adds signature and timestamp headers to webhook delivery attempts. Recipients must verify the exact raw signed bytes, compare signatures safely, enforce a timestamp window, and deduplicate delivery IDs. Public delivery/replay abuse-case evidence is still being expanded.

Headers:

  • X-Webhook-Signature: HMAC-SHA256 signature of the payload
  • X-Webhook-Timestamp: Unix timestamp in seconds since epoch (UTC)

Signature Format:

sha256={hmac_hex_digest}

Verification Example (Node.js):

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  // Create HMAC using webhook secret
  const hmac = crypto.createHmac('sha256', secret);

  // Update with stringified payload
  hmac.update(JSON.stringify(payload));

  // Generate expected signature
  const expectedSignature = `sha256=${hmac.digest('hex')}`;

  // Use timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express.js middleware example
app.post('/webhooks/sso', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const payload = req.body;

  // Limit the replay window; also persist and deduplicate delivery IDs
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - timestamp) > 300) {
    return res.status(400).json({ error: 'Timestamp too old' });
  }

  // Verify signature
  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook payload
  console.log('Received event:', payload.event);
  res.status(200).json({ status: 'received' });
});

Verification Example (Python):

import hmac
import hashlib
import time
import json

def verify_webhook_signature(payload, signature, secret):
    # Create HMAC using webhook secret
    hmac_gen = hmac.new(
        secret.encode('utf-8'),
        json.dumps(payload).encode('utf-8'),
        hashlib.sha256
    )

    # Generate expected signature
    expected_signature = f"sha256={hmac_gen.hexdigest()}"

    # Use constant-time comparison
    return hmac.compare_digest(signature, expected_signature)

# Flask example
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks/sso', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = int(request.headers.get('X-Webhook-Timestamp'))
    payload = request.json

    # Verify timestamp
    current_time = int(time.time())
    if abs(current_time - timestamp) > 300:
        return jsonify({'error': 'Timestamp too old'}), 400

    # Verify signature
    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401

    # Process webhook
    print(f"Received event: {payload['event']}")
    return jsonify({'status': 'received'}), 200

Security Best Practices:

  • Always verify signatures before processing webhook payloads
  • Validate timestamps to limit the replay window and deduplicate delivery IDs
  • Validate signature lengths and use constant-time comparison to reduce timing leakage
  • Store webhook secrets securely (environment variables, secret managers)
  • Use HTTPS endpoints only
  • Implement rate limiting on your webhook endpoints