> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Security Guide

> Comprehensive guide to securing StateSet webhooks with signature verification, replay protection, and security best practices

# Webhook Security Guide

Learn how to securely handle StateSet webhooks with comprehensive security patterns, signature verification, replay attack prevention, and production-ready implementation examples.

## Prerequisites

Before you begin, ensure you have:

* A StateSet account with webhook endpoints configured
* Understanding of HMAC signature verification
* HTTPS endpoint for receiving webhooks
* Basic knowledge of Node.js/Express (examples provided)
* Production environment security considerations

## Security Fundamentals

<Steps>
  <Step title="Webhook Secret Configuration">
    Configure your webhook secret in the StateSet dashboard:

    ```bash theme={null}
    # .env
    STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
    WEBHOOK_ENDPOINT_URL=https://api.yourstore.com/webhooks/StateSet

    # Security Settings
    WEBHOOK_TIMEOUT_MS=5000
    MAX_WEBHOOK_BODY_SIZE=1048576  # 1MB
    ENABLE_REPLAY_PROTECTION=true
    REPLAY_TOLERANCE_SECONDS=300   # 5 minutes
    ```
  </Step>

  <Step title="HTTPS Requirements">
    Ensure your webhook endpoint uses HTTPS:

    ```javascript theme={null}
    // webhook-server.js
    import express from 'express';
    import https from 'https';
    import fs from 'fs';

    const app = express();

    // Production HTTPS setup
    if (process.env.NODE_ENV === 'production') {
      const options = {
        key: fs.readFileSync('/path/to/private-key.pem'),
        cert: fs.readFileSync('/path/to/certificate.pem'),
        // Additional security headers
        secureProtocol: 'TLSv1_2_method',
        ciphers: [
          'ECDHE-RSA-AES128-GCM-SHA256',
          'ECDHE-RSA-AES256-GCM-SHA384',
          'ECDHE-RSA-AES128-SHA256',
          'ECDHE-RSA-AES256-SHA384'
        ].join(':'),
        honorCipherOrder: true
      };
      
      https.createServer(options, app).listen(443, () => {
        logger.info('Secure webhook server running on port 443');
      });
    } else {
      app.listen(3000, () => {
        logger.info('Development webhook server running on port 3000');
      });
    }
    ```
  </Step>

  <Step title="Request Validation Middleware">
    Implement comprehensive request validation:

    ```javascript theme={null}
    import rateLimit from 'express-rate-limit';
    import helmet from 'helmet';

    // Security middleware
    app.use(helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          scriptSrc: ["'none'"],
          styleSrc: ["'none'"],
          imgSrc: ["'none'"]
        }
      }
    }));

    // Rate limiting for webhook endpoints
    const webhookLimiter = rateLimit({
      windowMs: 1 * 60 * 1000, // 1 minute
      max: 100, // Limit each IP to 100 requests per windowMs
      message: 'Too many webhook requests from this IP',
      standardHeaders: true,
      legacyHeaders: false,
      skip: (req) => {
        // Skip rate limiting for known StateSet IPs
        const StateSetIPs = ['52.89.214.238', '54.187.174.169', '54.187.205.235'];
        return StateSetIPs.includes(req.ip);
      }
    });

    app.use('/webhooks', webhookLimiter);
    ```
  </Step>
</Steps>

## Signature Verification

### Basic HMAC Verification

Implement secure signature verification:

```javascript theme={null}
import crypto from 'crypto';
import express from 'express';

class WebhookVerifier {
  constructor(secret) {
    this.secret = secret;
    this.tolerance = 300; // 5 minutes in seconds
  }
  
  verifySignature(payload, signature, timestamp) {
    try {
      // Verify timestamp to prevent replay attacks
      const currentTime = Math.floor(Date.now() / 1000);
      if (Math.abs(currentTime - timestamp) > this.tolerance) {
        throw new Error('Webhook timestamp is too old');
      }
      
      // Create expected signature
      const expectedSignature = this.computeSignature(payload, timestamp);
      
      // Use constant-time comparison to prevent timing attacks
      return this.secureCompare(signature, expectedSignature);
      
    } catch (error) {
      logger.error('Signature verification failed:', error.message);
      return false;
    }
  }
  
  computeSignature(payload, timestamp) {
    const signedPayload = `${timestamp}.${payload}`;
    return crypto
      .createHmac('sha256', this.secret)
      .update(signedPayload, 'utf8')
      .digest('hex');
  }
  
  secureCompare(a, b) {
    if (a.length !== b.length) {
      return false;
    }
    
    let result = 0;
    for (let i = 0; i < a.length; i++) {
      result |= a.charCodeAt(i) ^ b.charCodeAt(i);
    }
    
    return result === 0;
  }
  
  extractSignature(header) {
    const elements = header.split(',');
    const signatures = {};
    
    for (const element of elements) {
      const [key, value] = element.split('=');
      if (key === 'v1') {
        signatures.v1 = value;
      } else if (key === 't') {
        signatures.timestamp = parseInt(value, 10);
      }
    }
    
    return signatures;
  }
}

// Initialize verifier
const verifier = new WebhookVerifier(process.env.STATESET_WEBHOOK_SECRET);

// Middleware to capture raw body
app.use('/webhooks/StateSet', express.raw({ type: 'application/json', limit: '1mb' }));

// Webhook verification middleware
function verifyWebhook(req, res, next) {
  const signature = req.headers['StateSet-signature'];
  const payload = req.body.toString();
  
  if (!signature) {
    return res.status(401).json({ error: 'Missing signature header' });
  }
  
  try {
    const { v1: sig, timestamp } = verifier.extractSignature(signature);
    
    if (!sig || !timestamp) {
      return res.status(401).json({ error: 'Invalid signature format' });
    }
    
    const isValid = verifier.verifySignature(payload, sig, timestamp);
    
    if (!isValid) {
      return res.status(401).json({ error: 'Invalid signature' });
    }
    
    // Parse JSON only after verification
    req.body = JSON.parse(payload);
    next();
    
  } catch (error) {
    logger.error('Webhook verification error:', error);
    return res.status(400).json({ error: 'Webhook verification failed' });
  }
}
```

### Advanced Signature Verification

Production-ready implementation with enhanced security:

```javascript theme={null}
class AdvancedWebhookVerifier extends WebhookVerifier {
  constructor(secret, options = {}) {
    super(secret);
    this.tolerance = options.tolerance || 300;
    this.enableReplayProtection = options.enableReplayProtection !== false;
    this.processedWebhooks = new Map(); // In production, use Redis
    this.maxCacheSize = options.maxCacheSize || 10000;
  }
  
  async verifyWebhookWithReplayProtection(payload, signature, timestamp, webhookId) {
    // Check for replay attacks
    if (this.enableReplayProtection && this.processedWebhooks.has(webhookId)) {
      throw new Error('Webhook has already been processed (replay attack detected)');
    }
    
    // Verify signature
    const isValid = this.verifySignature(payload, signature, timestamp);
    
    if (isValid && this.enableReplayProtection) {
      // Store webhook ID to prevent replay
      this.processedWebhooks.set(webhookId, Date.now());
      
      // Clean up old entries to prevent memory leaks
      this.cleanupCache();
    }
    
    return isValid;
  }
  
  cleanupCache() {
    if (this.processedWebhooks.size > this.maxCacheSize) {
      const cutoff = Date.now() - (this.tolerance * 1000);
      
      for (const [id, timestamp] of this.processedWebhooks.entries()) {
        if (timestamp < cutoff) {
          this.processedWebhooks.delete(id);
        }
      }
    }
  }
  
  // Verify multiple signature versions for backward compatibility
  verifyMultipleSignatures(payload, signatureHeader, timestamp) {
    const signatures = this.extractSignature(signatureHeader);
    
    // Try v1 signature first
    if (signatures.v1) {
      const isValid = this.secureCompare(
        signatures.v1,
        this.computeSignature(payload, timestamp)
      );
      if (isValid) return true;
    }
    
    // Fallback to other versions if needed
    // This allows for graceful migration between signature versions
    
    return false;
  }
}

// Production webhook handler
const advancedVerifier = new AdvancedWebhookVerifier(
  process.env.STATESET_WEBHOOK_SECRET,
  {
    tolerance: 300,
    enableReplayProtection: true,
    maxCacheSize: 10000
  }
);

async function advancedVerifyWebhook(req, res, next) {
  const signature = req.headers['StateSet-signature'];
  const webhookId = req.headers['StateSet-webhook-id'];
  const payload = req.body.toString();
  
  if (!signature || !webhookId) {
    return res.status(401).json({ 
      error: 'Missing required headers',
      required: ['StateSet-signature', 'StateSet-webhook-id']
    });
  }
  
  try {
    const { v1: sig, timestamp } = advancedVerifier.extractSignature(signature);
    
    const isValid = await advancedVerifier.verifyWebhookWithReplayProtection(
      payload, 
      sig, 
      timestamp, 
      webhookId
    );
    
    if (!isValid) {
      // Log security event
      console.warn('Webhook security violation:', {
        ip: req.ip,
        userAgent: req.headers['user-agent'],
        timestamp: new Date().toISOString(),
        webhookId,
        signatureProvided: !!signature
      });
      
      return res.status(401).json({ error: 'Webhook verification failed' });
    }
    
    req.body = JSON.parse(payload);
    req.webhookId = webhookId;
    next();
    
  } catch (error) {
    logger.error('Advanced webhook verification error:', error);
    return res.status(400).json({ error: error.message });
  }
}
```

## Event Processing Security

### Secure Event Handler

Implement secure and resilient event processing:

```javascript theme={null}
class SecureWebhookProcessor {
  constructor() {
    this.eventHandlers = new Map();
    this.processingQueue = [];
    this.maxRetries = 3;
    this.retryDelays = [1000, 5000, 15000]; // Progressive backoff
  }
  
  registerHandler(eventType, handler, options = {}) {
    this.eventHandlers.set(eventType, {
      handler,
      requiresAuth: options.requiresAuth !== false,
      timeout: options.timeout || 30000,
      retryable: options.retryable !== false
    });
  }
  
  async processWebhook(webhookData, context = {}) {
    const { event, data, id: webhookId } = webhookData;
    
    // Input validation
    if (!event || !data) {
      throw new Error('Invalid webhook payload structure');
    }
    
    const handlerConfig = this.eventHandlers.get(event);
    
    if (!handlerConfig) {
      console.warn(`No handler registered for event: ${event}`);
      return { status: 'ignored', reason: 'no_handler' };
    }
    
    // Security context validation
    if (handlerConfig.requiresAuth && !context.authenticated) {
      throw new Error('Authentication required for this event type');
    }
    
    return await this.executeWithRetry(
      handlerConfig,
      data,
      { ...context, event, webhookId }
    );
  }
  
  async executeWithRetry(handlerConfig, data, context) {
    let lastError;
    
    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        // Set timeout for handler execution
        const result = await Promise.race([
          handlerConfig.handler(data, context),
          this.timeoutPromise(handlerConfig.timeout)
        ]);
        
        return { 
          status: 'success', 
          result, 
          attempt: attempt + 1 
        };
        
      } catch (error) {
        lastError = error;
        
        logger.error(`Webhook handler failed (attempt ${attempt + 1});:`, {
          event: context.event,
          webhookId: context.webhookId,
          error: error.message,
          stack: error.stack
        });
        
        // Don't retry certain types of errors
        if (!handlerConfig.retryable || this.isNonRetryableError(error)) {
          break;
        }
        
        // Wait before retrying (except on last attempt)
        if (attempt < this.maxRetries) {
          await this.delay(this.retryDelays[attempt] || 15000);
        }
      }
    }
    
    throw new Error(
      `Webhook processing failed after ${this.maxRetries + 1} attempts: ${lastError.message}`
    );
  }
  
  timeoutPromise(timeout) {
    return new Promise((_, reject) => {
      setTimeout(() => reject(new Error('Handler timeout')), timeout);
    });
  }
  
  isNonRetryableError(error) {
    const nonRetryablePatterns = [
      /validation/i,
      /authentication/i,
      /authorization/i,
      /not found/i,
      /duplicate/i
    ];
    
    return nonRetryablePatterns.some(pattern => 
      pattern.test(error.message)
    );
  }
  
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Initialize processor and register handlers
const processor = new SecureWebhookProcessor();

// Order event handlers
processor.registerHandler('order.created', async (orderData, context) => {
  // Validate order data
  if (!orderData.id || !orderData.customer_email) {
    throw new Error('Invalid order data: missing required fields');
  }
  
  // Process order creation
  logger.info(`Processing order creation: ${orderData.id}`);
  
  // Your business logic here
  await processNewOrder(orderData);
  
  return { processed: true, orderId: orderData.id };
}, { timeout: 30000 });

processor.registerHandler('order.cancelled', async (orderData, context) => {
  // Handle order cancellation
  logger.info(`Processing order cancellation: ${orderData.id}`);
  
  await processOrderCancellation(orderData);
  
  return { processed: true, orderId: orderData.id };
});

processor.registerHandler('payment.failed', async (paymentData, context) => {
  // Handle payment failures (non-retryable)
  logger.info(`Processing payment failure: ${paymentData.payment_id}`);
  
  await handlePaymentFailure(paymentData);
  
  return { processed: true, paymentId: paymentData.payment_id };
}, { retryable: false });
```

### Complete Webhook Endpoint

Production-ready webhook endpoint with all security measures:

```javascript theme={null}
// Complete secure webhook endpoint
app.post('/webhooks/StateSet', 
  advancedVerifyWebhook,
  async (req, res) => {
    const startTime = Date.now();
    
    try {
      // Add request context
      const context = {
        ip: req.ip,
        userAgent: req.headers['user-agent'],
        timestamp: new Date().toISOString(),
        authenticated: true, // Set by verification middleware
        webhookId: req.webhookId
      };
      
      // Process webhook
      const result = await processor.processWebhook(req.body, context);
      
      // Log successful processing
      logger.info('Webhook processed successfully:', {
        event: req.body.event,
        webhookId: req.webhookId,
        processingTime: Date.now(); - startTime,
        result: result.status
      });
      
      // Return success response
      res.status(200).json({
        received: true,
        processed: result.status === 'success',
        webhookId: req.webhookId,
        processingTime: Date.now() - startTime
      });
      
    } catch (error) {
      // Log error with context
      logger.error('Webhook processing error:', {
        event: req.body?.event,
        webhookId: req.webhookId,
        error: error.message,
        stack: error.stack,
        processingTime: Date.now(); - startTime
      });
      
      // Return appropriate error response
      const statusCode = error.message.includes('authentication') ? 401 :
                        error.message.includes('validation') ? 400 : 500;
      
      res.status(statusCode).json({
        received: true,
        processed: false,
        error: error.message,
        webhookId: req.webhookId
      });
    }
  }
);
```

## IP Allowlisting

### StateSet IP Ranges

Configure IP allowlisting for enhanced security:

```javascript theme={null}
class IPAllowlist {
  constructor() {
    // StateSet webhook IP ranges (update as needed)
    this.allowedRanges = [
      '52.89.214.238/32',
      '54.187.174.169/32', 
      '54.187.205.235/32',
      // Add more StateSet IP ranges as provided
    ];
    
    this.compiledRanges = this.compileRanges();
  }
  
  compileRanges() {
    return this.allowedRanges.map(range => {
      const [ip, cidr] = range.split('/');
      const mask = ~(0xFFFFFFFF >>> parseInt(cidr, 10));
      return {
        network: this.ipToInt(ip) & mask,
        mask: mask
      };
    });
  }
  
  ipToInt(ip) {
    return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
  }
  
  isAllowed(ip) {
    const ipInt = this.ipToInt(ip);
    
    return this.compiledRanges.some(range => 
      (ipInt & range.mask) === range.network
    );
  }
}

// IP allowlist middleware
const ipAllowlist = new IPAllowlist();

function checkIPAllowlist(req, res, next) {
  // Skip in development
  if (process.env.NODE_ENV !== 'production') {
    return next();
  }
  
  const clientIP = req.ip || req.connection.remoteAddress;
  
  if (!ipAllowlist.isAllowed(clientIP)) {
    console.warn('Webhook request from unauthorized IP:', {
      ip: clientIP,
      userAgent: req.headers['user-agent'],
      timestamp: new Date().toISOString()
    });
    
    return res.status(403).json({ 
      error: 'IP not allowed',
      ip: clientIP 
    });
  }
  
  next();
}

// Apply IP allowlist before webhook processing
app.use('/webhooks/StateSet', checkIPAllowlist);
```

## Monitoring and Alerting

### Security Event Monitoring

Implement comprehensive security monitoring:

```javascript theme={null}
import winston from 'winston';

class WebhookSecurityMonitor {
  constructor() {
    this.logger = winston.createLogger({
      level: 'info',
      format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
      ),
      transports: [
        new winston.transports.File({ filename: 'webhook-security.log' }),
        new winston.transports.Console()
      ]
    });
    
    this.securityEvents = new Map();
    this.alertThresholds = {
      failedVerifications: 10,
      suspiciousIPs: 5,
      timeWindow: 300000 // 5 minutes
    };
  }
  
  logSecurityEvent(eventType, data) {
    const event = {
      type: eventType,
      timestamp: Date.now(),
      data: data
    };
    
    this.logger.warn('Security event detected', event);
    
    // Track events for alerting
    this.trackEventForAlerting(eventType, data);
  }
  
  trackEventForAlerting(eventType, data) {
    const key = `${eventType}:${data.ip || 'unknown'}`;
    const now = Date.now();
    
    if (!this.securityEvents.has(key)) {
      this.securityEvents.set(key, []);
    }
    
    const events = this.securityEvents.get(key);
    events.push(now);
    
    // Clean old events
    const cutoff = now - this.alertThresholds.timeWindow;
    const recentEvents = events.filter(timestamp => timestamp > cutoff);
    this.securityEvents.set(key, recentEvents);
    
    // Check for alert conditions
    this.checkAlertConditions(eventType, data, recentEvents.length);
  }
  
  checkAlertConditions(eventType, data, eventCount) {
    let shouldAlert = false;
    
    if (eventType === 'signature_verification_failed' && 
        eventCount >= this.alertThresholds.failedVerifications) {
      shouldAlert = true;
    }
    
    if (eventType === 'suspicious_ip' && 
        eventCount >= this.alertThresholds.suspiciousIPs) {
      shouldAlert = true;
    }
    
    if (shouldAlert) {
      this.sendSecurityAlert(eventType, data, eventCount);
    }
  }
  
  async sendSecurityAlert(eventType, data, eventCount) {
    const alert = {
      type: 'webhook_security_alert',
      eventType,
      count: eventCount,
      timeWindow: this.alertThresholds.timeWindow / 1000 / 60, // minutes
      data,
      timestamp: new Date().toISOString()
    };
    
    this.logger.error('Security alert triggered', alert);
    
    // Send to monitoring service (PagerDuty, Slack, etc.)
    try {
      await this.sendToMonitoringService(alert);
    } catch (error) {
      logger.error('Failed to send security alert:', error);
    }
  }
  
  async sendToMonitoringService(alert) {
    // Example: Send to Slack webhook
    if (process.env.SLACK_WEBHOOK_URL) {
      const message = {
        text: `🚨 Webhook Security Alert: ${alert.eventType}`,
        attachments: [{
          color: 'danger',
          fields: [
            { title: 'Event Type', value: alert.eventType, short: true },
            { title: 'Count', value: alert.count, short: true },
            { title: 'Time Window', value: `${alert.timeWindow} minutes`, short: true },
            { title: 'IP Address', value: alert.data.ip || 'Unknown', short: true }
          ],
          timestamp: Math.floor(Date.now() / 1000)
        }]
      };
      
      // Send to Slack (implement HTTP request)
      // await fetch(process.env.SLACK_WEBHOOK_URL, { ... });
    }
  }
}

// Initialize security monitor
const securityMonitor = new WebhookSecurityMonitor();

// Enhanced verification middleware with monitoring
function monitoredVerifyWebhook(req, res, next) {
  const signature = req.headers['StateSet-signature'];
  const webhookId = req.headers['StateSet-webhook-id'];
  const payload = req.body.toString();
  const clientIP = req.ip;
  
  if (!signature || !webhookId) {
    securityMonitor.logSecurityEvent('missing_headers', {
      ip: clientIP,
      userAgent: req.headers['user-agent'],
      missingHeaders: [
        !signature && 'StateSet-signature',
        !webhookId && 'StateSet-webhook-id'
      ].filter(Boolean)
    });
    
    return res.status(401).json({ 
      error: 'Missing required headers' 
    });
  }
  
  try {
    const { v1: sig, timestamp } = advancedVerifier.extractSignature(signature);
    
    const isValid = advancedVerifier.verifySignature(payload, sig, timestamp);
    
    if (!isValid) {
      securityMonitor.logSecurityEvent('signature_verification_failed', {
        ip: clientIP,
        userAgent: req.headers['user-agent'],
        webhookId,
        hasSignature: !!signature,
        hasTimestamp: !!timestamp
      });
      
      return res.status(401).json({ error: 'Invalid signature' });
    }
    
    req.body = JSON.parse(payload);
    req.webhookId = webhookId;
    next();
    
  } catch (error) {
    securityMonitor.logSecurityEvent('verification_error', {
      ip: clientIP,
      error: error.message,
      webhookId
    });
    
    return res.status(400).json({ error: 'Webhook verification failed' });
  }
}
```

## Testing Webhook Security

### Security Test Suite

Comprehensive test suite for webhook security:

```javascript theme={null}
// webhook-security.test.js
import crypto from 'crypto';
import request from 'supertest';
import app from '../webhook-server.js';

describe('Webhook Security', () => {
  const webhookSecret = 'test_webhook_secret';
  
  function createValidSignature(payload, timestamp) {
    const signedPayload = `${timestamp}.${payload}`;
    const signature = crypto
      .createHmac('sha256', webhookSecret)
      .update(signedPayload, 'utf8')
      .digest('hex');
    return `t=${timestamp},v1=${signature}`;
  }
  
  describe('Signature Verification', () => {
    it('should accept valid signatures', async () => {
      const payload = JSON.stringify({ event: 'order.created', data: { id: '123' } });
      const timestamp = Math.floor(Date.now() / 1000);
      const signature = createValidSignature(payload, timestamp);
      
      const response = await request(app)
        .post('/webhooks/StateSet')
        .set('StateSet-signature', signature)
        .set('StateSet-webhook-id', 'test-webhook-id')
        .send(payload)
        .expect(200);
      
      expect(response.body.received).toBe(true);
    });
    
    it('should reject invalid signatures', async () => {
      const payload = JSON.stringify({ event: 'order.created', data: { id: '123' } });
      
      await request(app)
        .post('/webhooks/StateSet')
        .set('StateSet-signature', 'invalid-signature')
        .set('StateSet-webhook-id', 'test-webhook-id')
        .send(payload)
        .expect(401);
    });
    
    it('should reject old timestamps', async () => {
      const payload = JSON.stringify({ event: 'order.created', data: { id: '123' } });
      const oldTimestamp = Math.floor(Date.now() / 1000) - 600; // 10 minutes ago
      const signature = createValidSignature(payload, oldTimestamp);
      
      await request(app)
        .post('/webhooks/StateSet')
        .set('StateSet-signature', signature)
        .set('StateSet-webhook-id', 'test-webhook-id')
        .send(payload)
        .expect(401);
    });
    
    it('should prevent replay attacks', async () => {
      const payload = JSON.stringify({ event: 'order.created', data: { id: '123' } });
      const timestamp = Math.floor(Date.now() / 1000);
      const signature = createValidSignature(payload, timestamp);
      const webhookId = 'unique-webhook-id';
      
      // First request should succeed
      await request(app)
        .post('/webhooks/StateSet')
        .set('StateSet-signature', signature)
        .set('StateSet-webhook-id', webhookId)
        .send(payload)
        .expect(200);
      
      // Second request with same ID should fail
      await request(app)
        .post('/webhooks/StateSet')
        .set('StateSet-signature', signature)
        .set('StateSet-webhook-id', webhookId)
        .send(payload)
        .expect(401);
    });
  });
  
  describe('Rate Limiting', () => {
    it('should rate limit excessive requests', async () => {
      const payload = JSON.stringify({ event: 'test.event', data: {} });
      const timestamp = Math.floor(Date.now() / 1000);
      
      // Make many requests quickly
      const requests = Array.from({ length: 150 }, (_, i) => {
        const signature = createValidSignature(payload, timestamp);
        return request(app)
          .post('/webhooks/StateSet')
          .set('StateSet-signature', signature)
          .set('StateSet-webhook-id', `webhook-${i}`)
          .send(payload);
      });
      
      const responses = await Promise.allSettled(requests);
      const rateLimited = responses.filter(r => 
        r.status === 'fulfilled' && r.value.status === 429
      );
      
      expect(rateLimited.length).toBeGreaterThan(0);
    });
  });
  
  describe('Input Validation', () => {
    it('should reject oversized payloads', async () => {
      const largePayload = 'x'.repeat(2 * 1024 * 1024); // 2MB
      const timestamp = Math.floor(Date.now() / 1000);
      const signature = createValidSignature(largePayload, timestamp);
      
      await request(app)
        .post('/webhooks/StateSet')
        .set('StateSet-signature', signature)
        .set('StateSet-webhook-id', 'test-webhook-id')
        .send(largePayload)
        .expect(413); // Payload too large
    });
    
    it('should reject malformed JSON', async () => {
      const invalidJson = '{ invalid json }';
      const timestamp = Math.floor(Date.now() / 1000);
      const signature = createValidSignature(invalidJson, timestamp);
      
      await request(app)
        .post('/webhooks/StateSet')
        .set('StateSet-signature', signature)
        .set('StateSet-webhook-id', 'test-webhook-id')
        .send(invalidJson)
        .expect(400);
    });
  });
});
```

## Best Practices Summary

### Security Checklist

<AccordionGroup>
  <Accordion title="Signature Verification">
    ✅ Always verify webhook signatures using HMAC-SHA256
    ✅ Use constant-time comparison to prevent timing attacks
    ✅ Implement timestamp validation to prevent replay attacks
    ✅ Store processed webhook IDs to prevent duplicates
  </Accordion>

  <Accordion title="Network Security">
    ✅ Use HTTPS for all webhook endpoints
    ✅ Implement IP allowlisting for StateSet IP ranges
    ✅ Use rate limiting to prevent abuse
    ✅ Validate request headers and content types
  </Accordion>

  <Accordion title="Error Handling">
    ✅ Log security events for monitoring
    ✅ Return appropriate HTTP status codes
    ✅ Implement retry logic with exponential backoff
    ✅ Set reasonable timeouts for webhook processing
  </Accordion>

  <Accordion title="Monitoring">
    ✅ Track failed verification attempts
    ✅ Alert on suspicious activity patterns
    ✅ Monitor processing times and error rates
    ✅ Log all webhook events with context
  </Accordion>
</AccordionGroup>

### Production Deployment

```bash theme={null}
# Environment variables for production
STATESET_WEBHOOK_SECRET=whsec_prod_9K2xL5pN...
WEBHOOK_TIMEOUT_MS=30000
MAX_WEBHOOK_BODY_SIZE=1048576
ENABLE_REPLAY_PROTECTION=true
REPLAY_TOLERANCE_SECONDS=300

# Security headers
ENABLE_HELMET=true
ENABLE_RATE_LIMITING=true
ALLOWED_IPS=52.89.214.238,54.187.174.169,54.187.205.235

# Monitoring
ENABLE_SECURITY_MONITORING=true
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/your/slack/webhook
ALERT_EMAIL=security@yourstore.com

# SSL/TLS
SSL_CERT_PATH=/path/to/ssl/cert.pem
SSL_KEY_PATH=/path/to/ssl/private-key.pem
```

## Next Steps

<CardGroup cols={2}>
  <Card title="API Testing Guide" icon="flask" href="/guides/api-testing">
    Learn how to test your webhook implementations
  </Card>

  <Card title="Monitoring Guide" icon="chart-line" href="/guides/monitoring">
    Set up comprehensive monitoring and alerting
  </Card>

  <Card title="Integration Patterns" icon="plug" href="/guides/integration-patterns">
    Best practices for integrating with external systems
  </Card>

  <Card title="Performance Optimization" icon="gauge-high" href="/guides/performance">
    Optimize webhook processing performance
  </Card>
</CardGroup>

## Conclusion

Implementing robust webhook security is critical for protecting your application and ensuring data integrity. This guide provides comprehensive security patterns including signature verification, replay protection, IP allowlisting, and monitoring.

Key takeaways:

* ✅ Always verify webhook signatures using HMAC-SHA256
* ✅ Implement replay attack prevention with timestamp validation
* ✅ Use HTTPS and IP allowlisting for network security
* ✅ Monitor security events and set up alerting
* ✅ Test your security implementation thoroughly

With these security measures in place, you can confidently process StateSet webhooks while maintaining the highest security standards.
