Zuplo logo
Back to all articles

Testing Webhooks and Events Using Mock APIs

June 2, 2025
56 min read
Adrian Machado
Adrian MachadoStaff Engineer

This comprehensive guide takes you from seeing your first webhook (in just 5 minutes!) to building a reusable testing workflow for every project. Follow the progression based on your needs. Start with the quick setup for immediate results, then advance through the systematic workflow for thorough validation.

Table of Contents#

Quick Start: See Your First Webhook in 5 Minutes#

Perfect for initial exploration and understanding how webhooks work.

Step 1: Create Your Mock Endpoint#

Visit Mockbin.io, Beeceptor or RequestBin and click "Create endpoint." These platforms instantly generate a unique URL that captures incoming HTTP requests. Copy the provided URL, something like https://your-webhook-endpoint.com/hook.

Step 2: Configure Your Webhook Source#

Paste your mock URL into a webhook-enabled service:

  • GitHub: Repository settings → Webhooks → Add your mock URL for push notifications
  • Stripe: Developer dashboard → Create webhook endpoint for payment events
  • Discord: Server channel → Integrations → Add webhook

Step 3: Trigger a Test Event#

Generate activity that triggers your webhook. Make a commit in GitHub, simulate a payment in Stripe's test mode, or use the "Send test webhook" button in most developer consoles.

Step 4: Inspect the Results#

Return to your mock endpoint's dashboard. You'll see the complete webhook payload with headers, timestamp, and JSON data structure displayed in real-time with syntax highlighting.

For even more advanced logging and analytics, exploring platforms like Zuplo can be beneficial. Check out the Zuplo Portal features for more information.

This approach is risk-free and perfect for initial exploration. Once you've seen how testing webhooks and events using mock APIs works, you can move to local development tunnels, automated testing, and security validation covered later in this guide.

Advanced Workflow: Production-Ready Testing in 6 Steps#

Once you understand the basics, use this systematic approach for thorough validation before production deployment.

Step 1: Set Up Your Mock Endpoint and Authentication#

Goal: Create a controlled environment for testing webhook authenticity and signature validation.

Configure your mock API to simulate webhook providers with proper authentication headers. Mock APIs provide complete independence from external services, eliminating rate limits and service availability concerns.

For complete signature validation implementation and security best practices, see the webhook security section.

Success Criteria: Your endpoint correctly validates webhook signatures and rejects unauthorized requests.

Step 2: Configure Error Response Scenarios#

Goal: Test how your application handles various HTTP error codes and failure conditions.

Set up your mock API to return different error responses based on request parameters. This tests your retry logic and error handling mechanisms.

app.post("/webhook-test/:scenario", (req, res) => {
  const { scenario } = req.params;

  switch (scenario) {
    case "timeout":
      setTimeout(() => res.status(200).json({}), 30000);
      break;
    case "server-error":
      res.status(500).json({ error: "Internal server error" });
      break;
    case "rate-limit":
      res.status(429).json({ error: "Too many requests" });
      break;
    default:
      res.status(200).json({ status: "success" });
  }
});

Success Criteria: Your webhook processor implements proper backoff strategies for 5xx responses and respects rate limiting headers.

Step 3: Validate Payload Structure and Data Types#

Goal: Ensure your application correctly processes expected data formats and handles malformed payloads gracefully.

Comprehensive payload validation prevents your application from crashing when receiving unexpected input formats. Incorporating API monitoring tools can help you detect and resolve issues promptly.

const validatePayload = (payload) => {
  const required = ["event_type", "timestamp", "data"];
  const missing = required.filter((field) => !payload[field]);

  if (missing.length > 0) {
    throw new Error(`Missing required fields: ${missing.join(", ")}`);
  }

  if (typeof payload.timestamp !== "number") {
    throw new Error("Invalid timestamp format");
  }
};

Success Criteria: Your system validates incoming data against expected schemas and responds with appropriate error messages for invalid payloads.

Step 4: Test Retry Mechanisms with Simulated Failures#

Goal: Verify that your webhook delivery system implements proper retry logic with exponential backoff.

Configure your mock API to fail initially, then succeed after a specific number of attempts. This simulates temporary network issues or processing delays.

Implementing proper response logic and adding rate limits can help prevent your system from being overwhelmed by retries.

Success Criteria: Failed webhook deliveries are retried with increasing delays, and successful processing stops retry attempts.

Step 5: Verify Idempotency and Duplicate Handling#

Goal: Ensure your application processes duplicate webhook deliveries correctly without side effects.

Send identical payloads multiple times to test idempotency mechanisms. Proper webhook testing includes verifying that duplicate events don't cause unintended consequences.

Success Criteria: Duplicate webhook deliveries are detected and handled without creating duplicate records or triggering duplicate actions.

Step 6: Load Test with Burst Traffic Simulation#

Goal: Validate that your webhook receiver can handle sudden spikes in traffic without losing events.

Use your mock API to simulate high-volume webhook deliveries that mirror real-world traffic patterns.

Success Criteria: Your system maintains performance under load and implements appropriate rate limiting without dropping legitimate webhook events.

When to Use Each Approach#

Quick Start (Steps 1-4)Advanced Workflow (Steps 1-6)
Perfect for: Initial webhook exploration Rapid prototyping Understanding payload structures Quick integration testingEssential for: Production-ready applications Complex business logic validation Security-critical implementations Enterprise deployments

Both approaches complement each other. Start quick to understand the fundamentals, then implement the systematic workflow for comprehensive validation.

How Webhooks and Mock APIs Work Together#

Webhooks flip the traditional request-response model on its head. When an event occurs—payment processed, file uploaded, user registered—the source system immediately pushes a JSON payload to your predefined endpoints. No waiting, no asking. It's like having a personal assistant who proactively tells you what you need to know instead of you constantly checking in.

Why Push Beats Pull Every Time#

Polling wastes resources. Your application repeatedly asks "Has anything changed?" creating unnecessary network traffic and latency. Webhooks deliver the answer before you ask the question.

When something meaningful happens, the event producer immediately notifies all registered consumers via HTTP POST requests. You get real-time updates with minimal overhead. Notifications arrive within seconds instead of minutes between polling cycles.

Webhook Architecture Components#

The event producer maintains a subscriber registry with endpoint URLs and event preferences. When a triggering event occurs, it serializes event data into JSON, adds authentication headers and metadata, then fires HTTP requests to each registered endpoint.

Your consumer application parses the payload and executes business logic. Common authentication mechanisms include HMAC signatures with shared secrets, bearer tokens, or mutual TLS certificates. Headers contain signature verification data, event types, delivery timestamps, and unique identifiers for deduplication. Using tools like federated gateways can further enhance developer productivity in managing complex webhook architectures.

Mock APIs Fill the Testing Gap#

Mock APIs simulate the receiving end during development and testing. Tools like Mockbin.io provide controllable endpoints that validate incoming payloads, verify authentication headers, and return specific response codes to test retry logic.

These predictable testing environments let you simulate successful processing, authentication failures, timeouts, and malformed responses—all without depending on external services.

Webhook Testing: Local Development vs Production-Ready Solutions#

Testing webhooks effectively requires different approaches for local development versus production deployments. Ngrok excels at local debugging, while cloud-based API gateways like Zuplo offer instant production-grade endpoints.

Local Development with Ngrok#

For pure local debugging and development, ngrok creates a real tunnel that allows external services to reach your running application—no simulation required.

Installation and Setup#

Download ngrok and extract the executable to a directory in your system PATH. After creating a free account, authenticate your installation:

# Install and authenticate
ngrok config add-authtoken YOUR_AUTH_TOKEN

# Expose local server to generate HTTP and HTTPS URLs
ngrok http 3000

# Free accounts generate random URLs each session
# Request persistent subdomain (requires paid plan)
ngrok http 3000

# Use persistent subdomain (paid plan)
ngrok http 3000 --subdomain=your-webhook-test

Dashboard Inspection and Debugging#

Access http://127.0.0.1:4040 to inspect webhook traffic in real-time. The replay feature lets you resend any request to your application without triggering the original webhook source—invaluable for debugging complex webhook logic.

Ngrok vs Cloud-Based API Gateways#

Hosted API gateways like Zuplo excel at instant deployment and team collaboration, while ngrok offers superior debugging for complex webhook logic. You're testing against your actual application code with ngrok, but Zuplo provides production-ready endpoints with built-in security and monitoring.

// Zuplo webhook handler
export default async function handler(request) {
  const payload = await request.json();
  console.log("Webhook received:", payload);
  return new Response("OK", { status: 200 });
}

How to Catch Webhook Failures Before They Break Production#

Imagine catching webhook issues before they cost you real customers or revenue. That's exactly what integrating tests into your CI/CD pipeline accomplishes—reducing the debugging cycle from days to minutes. Instead of that sinking feeling when you discover your payment webhooks silently failing in production, you'll catch signature validation errors, payload handling issues, and timeout problems during development. Your future self (and your team) will thank you!

Here's a battle-tested GitHub Actions workflow for comprehensive webhook testing:

# .github/workflows/webhook-tests.yml
name: Webhook Integration Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  webhook-tests:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'

    - name: Install dependencies
      run: npm ci

    - name: Start mock webhook server
      run: |
        npm run start:mock-server &
        sleep 5  # Wait for server to start

    - name: Run webhook signature tests
      run: npm run test:webhook-signatures

    - name: Test error scenarios
      run: npm run test:webhook-errors

    - name: Test payload validation
      run: npm run test:webhook-payloads

    - name: Export test fixtures
      run: |
        mkdir -p test/fixtures
        cp test-results/webhook-payloads.json test/fixtures/

    - name: Validate idempotency
      run: npm run test:webhook-idempotency

Not using GitHub Actions? No problem. This approach works well with other CI platforms—GitLab CI users can adapt the syntax to use stages and script blocks, while CircleCI fans can implement similar patterns with their workflows and jobs structure, as outlined in mastering automated testing in CI/CD pipelines.

The fixture export step is particularly clever—it captures real webhook payloads during tests, using them as fixtures for lightning-fast unit tests later. This ensures your test data stays realistic even as webhook schemas evolve.

Automated webhook testing handles the repetitive validation while freeing you to focus on complex edge cases. Your tests should verify more than just successful processing—check event ordering, idempotent handling of duplicates, and graceful degradation under load. Integrating these tests into your CI/CD pipeline transforms webhook reliability from an afterthought to a deployment requirement.

For testing real application behavior, debugging webhook interactions, or validating end-to-end integrations, ngrok remains unmatched. When testing webhooks and events specifically, it's the developer's tool of choice for reliable local development environments.

Tweet

Over 10,000 developers trust Zuplo to secure, document, and monetize their APIs

Learn More

Building an Unbreakable Defense With Secure Webhook Validation#

Think of webhook security like protecting your home—without proper validation, you're essentially leaving your front door wide open. In the next few sections, we’ll cover everything from basic signature validation to advanced attack prevention, with practical code examples and testing strategies.

HTTPS Enforcement#

Requirement: All webhook endpoints must use HTTPS. Reject HTTP requests entirely.

// Middleware to enforce HTTPS
app.use((req, res, next) => {
  if (req.header("x-forwarded-proto") !== "https") {
    return res.status(400).json({ error: "HTTPS required" });
  }
  next();
});

Testing: Verify your endpoints respond appropriately to SSL/TLS handshake issues and certificate problems.

Signature Validation with HMAC SHA-256#

Implementation: Verify webhook signatures using HMAC with SHA-256. Avoid vulnerable algorithms like SHA-1 and MD5.

Node.js Implementation:

const crypto = require("crypto");

function validateSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");

  const expectedHeader = `sha256=${expectedSignature}`;

  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedHeader),
  );
}

app.post("/webhook", (req, res) => {
  const signature = req.headers["x-hub-signature-256"];
  const payload = JSON.stringify(req.body);

  if (!validateSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // Process webhook
  res.status(200).json({ status: "authenticated" });
});

Python Implementation with Replay Attack Prevention:

import hmac
import hashlib
import time
from datetime import datetime, timedelta

def validate_webhook(payload, signature, secret, timestamp_header):
    # Verify timestamp to prevent replay attacks
    try:
        timestamp = int(timestamp_header)
        current_time = int(time.time())

        # Reject webhooks older than 5 minutes
        if abs(current_time - timestamp) > 300:
            return False, "Timestamp too old"
    except (ValueError, TypeError):
        return False, "Invalid timestamp"

    # Verify signature
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        f"{timestamp}.{payload}".encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    received_signature = signature.replace('sha256=', '')

    if not hmac.compare_digest(expected_signature, received_signature):
        return False, "Invalid signature"

    return True, "Valid"

JSON Schema Validation#

Purpose: Validate incoming payloads match your expected structure to prevent processing malformed data.

const Ajv = require("ajv");
const ajv = new Ajv();

const webhookSchema = {
  type: "object",
  required: ["event_type", "timestamp", "data"],
  properties: {
    event_type: { type: "string" },
    timestamp: { type: "number", minimum: 0 },
    data: { type: "object" },
    id: { type: "string" },
  },
  additionalProperties: false,
};

const validatePayload = ajv.compile(webhookSchema);

app.post("/webhook", (req, res) => {
  // Signature validation first (from above)

  if (!validatePayload(req.body)) {
    return res.status(400).json({
      error: "Invalid payload structure",
      details: validatePayload.errors,
    });
  }

  // Process valid webhook
  res.status(200).json({ status: "processed" });
});

IP Verification and Allowlisting#

const allowedIPs = [
  "192.30.252.0/22", // GitHub webhook IPs
  "185.199.108.0/22",
];

function isIPAllowed(clientIP, allowedRanges) {
  // Implementation depends on your IP range checking library
  // Consider using 'ip-range-check' npm package
  return allowedRanges.some((range) => ipInRange(clientIP, range));
}

app.post("/webhook", (req, res) => {
  const clientIP = req.ip || req.connection.remoteAddress;

  if (!isIPAllowed(clientIP, allowedIPs)) {
    return res.status(403).json({ error: "Unauthorized IP" });
  }

  // Continue with other validations...
});

Secret Rotation with Zero Downtime#

class WebhookValidator {
  constructor() {
    this.secrets = [
      process.env.WEBHOOK_SECRET_CURRENT,
      process.env.WEBHOOK_SECRET_PREVIOUS, // For graceful rotation
    ];
  }

  validateSignature(payload, signature) {
    return this.secrets.some((secret) => {
      if (!secret) return false;

      const expectedSignature = crypto
        .createHmac("sha256", secret)
        .update(payload, "utf8")
        .digest("hex");

      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(`sha256=${expectedSignature}`),
      );
    });
  }
}

Rate Limiting and Resource Protection#

const rateLimit = require("express-rate-limit");

const webhookLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: {
    error: "Too many webhook requests",
    retryAfter: "15 minutes",
  },
  standardHeaders: true,
  legacyHeaders: false,
});

app.use("/webhook", webhookLimiter);

Response Logic and Error Handling#

Your response codes control sender retry behavior:

app.post("/webhook", async (req, res) => {
  try {
    // All security validations pass...

    await processWebhook(req.body);

    // Success - don't retry
    res.status(200).json({ status: "processed" });
  } catch (error) {
    if (error.type === "VALIDATION_ERROR") {
      // Client error - don't retry
      res.status(400).json({ error: error.message });
    } else if (error.type === "TEMPORARY_ERROR") {
      // Server error - retry with backoff
      res.status(500).json({ error: "Temporary processing error" });
    } else {
      // Unknown error - retry
      res.status(500).json({ error: "Internal server error" });
    }
  }
});

Testing Your Security Implementation#

Implementing webhook security measures is only half the battle—you need systematic testing to ensure they actually work under real-world conditions. Even the most carefully coded security can fail when faced with unexpected attack patterns or edge cases. This section walks you through comprehensive testing strategies that validate each security layer before production deployment.

1. Essential Security Test Checklist#

Before deploying to production, systematically verify each security measure works as expected. This checklist ensures no security gaps slip through:

  • HTTPS Enforcement: Test HTTP requests are rejected
  • Signature Validation: Test with tampered payloads, missing signatures, incorrect secrets
  • IP Verification: Test requests from unauthorized IPs
  • Timestamp Validation: Test with expired and future timestamps
  • Rate Limiting: Test with burst traffic patterns
  • Malformed Payloads: Test with invalid JSON, missing fields, wrong data types

2. Attack Simulation Testing#

Create test scenarios that mirror actual attack patterns you might face in production. This proactive approach helps you discover vulnerabilities before attackers do:

// Test endpoint for simulating various attack scenarios
app.post("/webhook-test/:scenario", (req, res) => {
  const { scenario } = req.params;

  switch (scenario) {
    case "invalid-signature":
      // Test signature validation
      const tamperedPayload = JSON.stringify({ ...req.body, malicious: true });
      return res.status(401).json({ error: "Invalid signature" });

    case "replay-attack":
      // Test timestamp validation
      const oldTimestamp = Math.floor(Date.now() / 1000) - 3600; // 1 hour old
      return res.status(401).json({ error: "Timestamp too old" });

    case "rate-limit-test":
      // Simulate rate limiting
      return res.status(429).json({
        error: "Too many requests",
        "retry-after": "60",
      });

    case "malformed-json":
      // Test payload validation
      return res.status(400).json({ error: "Invalid JSON structure" });

    default:
      return res.status(200).json({ status: "test-success" });
  }
});

This testing endpoint lets you simulate different attack scenarios without exposing your production systems to actual threats.

3. Load Testing for Security#

Security measures can break down under high load, so test your defenses with realistic traffic patterns:

// Use Artillery or similar tools with this configuration
// artillery-config.yml
/*
config:
  target: 'https://your-webhook-endpoint.com'
  phases:
    - duration: 60
      arrivalRate: 10
    - duration: 120
      arrivalRate: 50  # Simulate burst traffic
scenarios:
  - name: "Webhook Security Test"
    requests:
      - post:
          url: "/webhook"
          headers:
            x-hub-signature-256: "sha256=invalid_signature"
            content-type: "application/json"
          json:
            event_type: "test"
            timestamp: "{{ $timestamp }}"
            data: {}
*/

Security Metrics and Monitoring#

Tracking security metrics helps you detect attack patterns and identify weaknesses in your defenses. These metrics serve as early warning indicators for potential security incidents.

Security Metrics to Track#

Monitor these critical security events to understand your webhook security posture:

  • Invalid signature attempts: Indicates potential tampering or credential theft
  • Rate limit violations: Shows traffic spikes that could be attacks or misconfigured clients
  • Unauthorized IP access: Reveals potential reconnaissance or bypass attempts
  • Malformed payload submissions: Suggests probing for input validation vulnerabilities
  • Replay attack attempts: Indicates sophisticated attackers using captured requests

Implementing Security Monitoring#

const securityMetrics = {
  invalidSignatures: 0,
  rateLimitHits: 0,
  unauthorizedIPs: 0,
  malformedPayloads: 0,
  replayAttempts: 0,
};

// Middleware to track security events
app.use("/webhook", (req, res, next) => {
  res.on("finish", () => {
    if (res.statusCode === 401) securityMetrics.invalidSignatures++;
    if (res.statusCode === 429) securityMetrics.rateLimitHits++;
    if (res.statusCode === 403) securityMetrics.unauthorizedIPs++;
    if (res.statusCode === 400) securityMetrics.malformedPayloads++;

    // Alert if thresholds exceeded
    if (securityMetrics.invalidSignatures > 10) {
      alertSecurityTeam("High number of invalid signature attempts");
    }
  });
  next();
});

This monitoring code automatically tracks security events and triggers alerts when attack patterns emerge. The response codes tell you exactly what type of security violation occurred, allowing for targeted incident response.

Setting Up Alerts#

Configure alerts for unusual patterns that might indicate attacks:

  • Sudden spikes in invalid signatures: Could indicate compromised webhook secrets
  • High rate limit violations from single IPs: Possible denial of service attempts
  • Multiple unauthorized IP attempts: May indicate IP allowlist bypass attempts
  • Unusual geographic request patterns: Could suggest credential theft or bot networks

Regular monitoring of these metrics helps you stay ahead of security threats and validates that your security measures are working as intended.

Troubleshooting: Why Isn't My Webhook Firing?#

SymptomLikely CauseQuick FixVerification
No webhooks receivedIncorrect endpoint URLVerify webhook URL in provider settingsCheck provider logs for delivery attempts
Intermittent failuresNetwork timeoutsIncrease timeout settings, optimize response timeMonitor response times in logs
401/403 errorsAuthentication failureVerify signature validation, check secretsTest with curl using correct headers
404 errorsWrong endpoint pathDouble-check URL path and routingTest endpoint directly with browser/Postman
SSL/TLS errorsCertificate issuesEnsure valid HTTPS certificateUse SSL checker tools
Payload appears emptyContent-type mismatchVerify application/json content-type headerLog raw request body
Webhooks work locally, fail in productionFirewall blockingWhitelist provider IPs, check security groupsTest from external IP

Ngrok Issues#

If you're testing locally with ngrok:

  • Tunnel inactive: Restart ngrok with ngrok http 3000 and update webhook URL.
  • Can't access dashboard: Navigate to http://127.0.0.1:4040 to inspect requests.
  • Connection refused: Your local server must be running before starting ngrok.
  • Rate limiting: Free accounts have limits; upgrade or reduce test frequency.

Systematic Debugging#

  1. Verify basics: Confirm your endpoint is publicly accessible and returns 200 OK.
  2. Examine provider logs: Review delivery attempts, response codes, and retry schedules.
  3. Test manually: Send test payloads to your endpoint with Postman.
  4. Check timing: Respond within 10-30 seconds to avoid timeout failures.
  5. Validate payload handling: Test your handler with various payload formats.

Testing Tools#

  • Mockbin.io: Configure custom responses and mock full APIs using OpenAPI documents.
  • Webhook.site: Capture and inspect webhook payloads without local setup.
  • RequestBin: Similar capture tool for debugging payload issues.
  • Postman: Manual webhook testing and payload validation.
  • Provider testing interfaces: Many services offer built-in testing tools.

Webhook monitoring and testing requires ongoing attention. Set up proper logging and alerting to catch failures before they impact users.

Manage Reliable Webhooks with Zuplo#

Zuplo's programmable API gateway handles webhook management with minimal code changes, letting you focus on business logic rather than infrastructure. This allows you to concentrate on initiatives like AI API monetization, accelerating your product's time to market. Edge execution across 300+ global data centers ensures low-latency webhook processing regardless of user or webhook provider location. Want blazing-fast webhook processing? Zuplo deploys your security policies across 300 data centers worldwide in less than 5 seconds!

Zuplo's SOC2 Type 2 Compliance provides enterprise-grade security that aligns with the security best practices covered in this guide. Built-in capabilities for rate limiting, authentication, and request validation reduce the complexity of implementing secure webhook endpoints.

Start with the quick-start methods covered earlier, then gradually incorporate more sophisticated testing and security measures. Comprehensive webhook testing reduces production incidents and improves system reliability. By ensuring reliable and secure webhooks, you can confidently pursue API monetization strategies that drive revenue and growth. Deploy webhook security with Zuplo across 300+ data centers in under 5 seconds.