← All Articles ยท ยท 2 min read

Webhook Testing: How to Debug, Inspect, and Replay Webhooks in 2026

Complete guide to webhook testing. Covers local tunneling, replay tools, signature verification, retry handling, and the best practices for testing webhooks without production data.

webhookapitestingdebugginghttp

Webhook Testing: How to Debug, Inspect, and Replay Webhooks in 2026

What is a Webhook?

A webhook is an HTTP POST request sent from one service to another when an event happens. Your server receives it and reacts.

Stripe event happens (payment.success)
  โ†’ POST https://yoursite.com/api/webhook/stripe
  โ†’ Your server processes it

Testing Webhooks Locally

Step 1: Expose Localhost

# ngrok (free tier)
ngrok http 3000

# cloudflared (Cloudflare, free)
cloudflared tunnel --url http://localhost:3000

# Both give you a public URL like:
# https://abc123.ngrok.io

Step 2: Register the Webhook

In your service dashboard (Stripe, GitHub, Slack):

Payload URL: https://abc123.ngrok.io/api/webhook/handler
Events: payment_intent.succeeded, payment_intent.failed

Verifying Webhook Signatures

Always verify signatures. Never trust the payload blindly.

// Express.js webhook handler with Stripe signature verification
const express = require('express');
const crypto = require('crypto');

const app = express();

app.post('/api/webhook/stripe', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

  try {
    const event = crypto.timingSafeEqual(
      Buffer.from(sig),
      Buffer.from(webhookSecret)
    );
    // โœ… Verified
    const payload = JSON.parse(req.body);
    handleEvent(payload);
    res.json({ received: true });
  } catch (err) {
    res.status(400).send(`Webhook Error: ${err.message}`);
  }
});

Replaying Webhooks

# With Stripe CLI
stripe trigger payment_intent.succeeded

# Or replay a specific event
stripe events resend evt_1234567890

Common Webhook Mistakes

MistakeFix
Responding with non-2xxAlways return 200 quickly, process async
Not handling duplicatesUse idempotency keys
Forgetting to verify signaturesAlways verify, always
Long-running processingQueue the event, respond immediately

Retry Logic

Services like Stripe retry failed webhooks:

Attempt 1: Immediate
Attempt 2: 5 minutes later
Attempt 3: 30 minutes later
Attempt 4: 1 hour later
Attempt 5: 2 hours later
Total: ~3.5 hours before giving up

Your handler should:

  1. Return 200 immediately
  2. Queue the event for processing
  3. Process in background

Testing Tools

ToolUse Case
ngrokLocal tunneling
RequestBinCapture and replay
Webhook.siteFree webhook testing
Stripe CLIStripe-specific testing
GitHub CLIGitHub webhook testing

Free Newsletter

Level Up Your Dev Workflow

Get new tools, guides, and productivity tips delivered to your inbox.

Plus: grab the free Developer Productivity Checklist when you subscribe.

Found this guide useful? Check out our free developer tools.