How to Test Webhooks Locally and in Production: The Complete Guide
Webhooks are HTTP callbacks: a remote service sends a POST request to your server when something happens. Testing them is awkward by default — your local development server isn't reachable from the internet, payloads are one-shot, and debugging a failed delivery means convincing Stripe or GitHub to resend it.
This guide covers four phases of the webhook development lifecycle and the right tool for each.
Phase 1: Local Development
The standard tool is ngrok — it tunnels requests from a public URL to your localhost:3000.
npx ngrok http 3000
# Forwarding: https://abc123.ngrok.io → http://localhost:3000
# Register this URL in your Stripe/GitHub/etc. dashboard
# https://abc123.ngrok.io/api/webhooks/stripe
The ngrok dashboard at http://localhost:4040 shows every request in real time — headers, body, timing, and the ability to replay any request with one click. This is invaluable when your handler returns a 500 and you want to iterate without retrieving the event again.
Other options: Cloudflare Tunnel (cloudflared tunnel) is free with no time limits, and VS Code Dev Tunnels (built into the editor) require no install at all.
Phase 2: Writing the Handler
A production-grade webhook handler does three things before any business logic:
- Validates the signature. Every major platform (Stripe, GitHub, Twilio) signs requests with HMAC-SHA256. Reject unsigned requests immediately.
- Acknowledges receipt fast. Return
200 OKwithin two seconds. Move slow work to a background queue. - Handles duplicates idempotently. Webhooks are delivered at-least-once. Store the event ID and skip re-processing if you've seen it before.
import { createHmac, timingSafeEqual } from "crypto"
function verifySignature(payload: string, signature: string, secret: string) {
const expected = createHmac("sha256", secret).update(payload).digest("hex")
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
}
export async function POST(req: Request) {
const body = await req.text()
const sig = req.headers.get("stripe-signature") ?? ""
if (!verifySignature(body, sig, process.env.STRIPE_WEBHOOK_SECRET!)) {
return new Response("Unauthorized", { status: 401 })
}
const event = JSON.parse(body)
// enqueue for async processing, return immediately
await queue.enqueue(event)
return new Response("OK", { status: 200 })
}
Phase 3: Integration Testing
With moqapi.dev webhooks, you configure delivery endpoints and test against real HTTPS URLs in staging. The platform provides built-in test delivery — hit the Test button on any webhook config and it sends a sample payload immediately, logging the request, response, and latency.
For CI, keep a staging endpoint running and add a job that sends a test payload after every deploy:
curl -X POST https://api.yourapp.com/api/webhooks/test -H "Authorization: Bearer $TOKEN" -d '{"webhookId": "wh_abc123"}'
Phase 4: Production Monitoring
Once live, you need visibility into:
- Delivery success rate — what percentage of webhooks are acknowledged with 2xx?
- Retry storms — failed deliveries trigger retries; a broken handler can cause hundreds of duplicate events.
- Latency — webhook providers give up after 5–30 seconds depending on the platform.
moqapi.dev's unified log viewer captures every webhook delivery attempt, status code, and response time in one searchable view. Filter by webhook ID, date range, or status to spot degraded endpoints before they fully fail.
Quick Reference: Webhook Signature Libraries
- Stripe:
stripe.webhooks.constructEvent() - GitHub:
@octokit/webhooks - Twilio:
twilio.validateRequest() - Custom:
crypto.createHmac("sha256", secret).update(payload).digest("hex")
About the Author
Founder and sole developer of moqapi.dev. Full-stack engineer with deep experience in API platforms, serverless runtimes, and developer tooling. Built moqapi to solve the mock data and deployment friction she experienced firsthand building production APIs.
Related Articles
What Is Mock Data and Why It Matters for Modern Development
Understand mock data, its role in frontend and backend testing, and how moqapi.dev automates the creation of realistic test payloads for every API endpoint.
API Testing Strategies for Modern Engineering Teams
Contract tests, snapshot tests, fuzz testing — explore the testing matrix every team needs, with examples using Node.js, Python, and moqapi.dev.
API Mocking vs Stubbing vs Faking: The Developer's Definitive Guide
These three terms are used interchangeably but mean very different things. Understand when to use each technique and how they affect your test quality.
Ready to build?
Start deploying serverless functions in under a minute.