Introduction
Testing webhooks locally is harder than testing a normal API because you do not control when the request starts. A webhook provider needs a public callback URL, and your localhost endpoint is usually not reachable from Stripe, GitHub, Shopify, Slack, Twilio, or any other external service. That creates three recurring problems: getting traffic into your development environment, seeing the full request details, and making sure signatures, retries, and headers behave the same way they do in production.
This guide shows practical ways to test webhooks locally without guessing. You will see when a localhost-only mock is enough, when a tunnel like ngrok makes sense, when a relay such as Webhook.site or ReqPour is the better fit, and when replay workflows help you debug real payloads. The goal is to help you inspect payloads, verify signatures, test retry behavior, and avoid setup mistakes that hide bugs until staging or production.
The approach is tool-agnostic first, then it compares ngrok, Webhook.site, and ReqPour so you can choose the right workflow for development, QA, and staging. If you want a broader overview, see the webhook testing guide and the deeper walkthrough on local webhook testing.
What it means to test webhooks locally
To test webhooks locally means validating your webhook handler on localhost before you deploy it. A provider like Stripe or GitHub sends an HTTP request to your webhook endpoint, your app reads the payload and HTTP headers, then returns a quick acknowledgment. That response usually confirms receipt; it does not work like a normal API call that returns data on demand.
Because localhost is not publicly reachable, external services need a tunnel or relay to deliver real events. You can test in three ways: replay a captured request with curl, send a simulated request from Postman, or receive a real provider event through a tunnel. For webhook endpoint testing and how to test webhook integrations, keep the raw body intact: signature verification often depends on the exact bytes used to compute the HMAC.
Can webhooks be sent to localhost?
Not directly from an external provider. localhost only works on your own machine, so Stripe, GitHub, Shopify, Slack, and Twilio cannot reach it unless you expose it through a tunnel, reverse proxy, or webhook relay.
For local development, the safest options are:
- a tunnel such as ngrok that forwards a public HTTPS URL to your local port
- a webhook relay such as ReqPour that captures, routes, and can replay deliveries
- a mock webhook or fixture for unit tests and offline development
If you only need to validate your handler logic, a mock is enough. If you need to test real event delivery, retries, and provider-specific headers, use a tunnel or relay.
Why webhooks fail in development
External services cannot call localhost directly, so you need a public callback URL during local testing. Tools like webhook development tools or a tunnel expose your development environment, which is why Stripe, GitHub, Shopify, Slack, and Twilio can reach it over HTTPS.
Most failures come from simple setup errors: a stale callback URL, the wrong port, missing environment variables, or a tunnel that changed after restart. Signature verification also breaks when middleware parses the body, trims whitespace, or uses the wrong secret, so the raw request no longer matches what the provider signed.
A webhook can also look broken when your handler times out, returns a non-2xx status, or rejects a payload shape the provider actually sends. Without request inspection and logs, these issues are easy to miss; webhook testing guide helps you trace the exact request before you guess.
Ways to test webhooks locally
Use curl or Postman when you want to quickly validate your handler, headers, and request parsing without involving a provider. Use mocks or fixtures for deterministic unit tests, especially for retries, bad signatures, malformed JSON, and idempotency checks. Use a tunnel such as ngrok or a webhook relay when Stripe, GitHub, or Shopify must send real events to your local machine. Use capture tools like Webhook.site or ReqPour when you need to inspect the exact payload, headers, and delivery history before wiring your app. For a deeper setup guide, see local webhook testing and webhook development tool local testing.
The best workflow combines methods: manual requests for fast checks, fixtures for edge cases, and a tunnel plus capture tool for full end-to-end debugging.
How to expose a local server to receive webhooks
A tunnel maps a public URL to localhost, so a provider can reach your local webhook endpoint without changing your app. For example, ngrok creates a temporary HTTPS URL and forwards requests to your local port, which solves reachability for real event delivery from Stripe, GitHub, or Shopify. A tunnel is simpler than a reverse proxy: a reverse proxy usually sits in front of servers you already expose, while a tunnel opens an outbound connection from your machine and relays traffic back in.
A webhook relay does the same job but adds webhook-specific controls: capture, inspect, route, and sometimes replay events. That makes tools like ReqPour useful when you test webhooks locally often, or across staging and production. See local testing tools and webhook relay for local testing for setup details.
How ngrok helps test webhooks
ngrok is useful when you want a fast way to expose a local server and receive real provider traffic. It gives you a public HTTPS callback URL, forwards requests to your local port, and lets you keep your webhook endpoint running on localhost while Stripe, GitHub, Shopify, Slack, or Twilio send event delivery traffic to it.
ngrok is especially helpful during development when you need to:
- test a new webhook endpoint before deployment
- confirm that HTTPS, headers, and payloads arrive intact
- verify signature verification against the raw body
- reproduce retry logic by returning non-2xx responses
The main tradeoff is that the URL can change when you restart the tunnel unless you use a reserved domain. That means you may need to update the callback URL in the provider dashboard during development.
Step-by-step: test webhooks locally with a relay or tunnel
Create a dedicated route such as /webhooks/stripe or /webhooks/github in Node.js/Express or Python/Flask, and confirm your server listens on the expected port before you expose it. Keep the handler separate from normal app routes so you can inspect the raw body, verify HTTP headers, and return the right status code without side effects.
Start a tunnel or relay with ngrok or ReqPour, copy the public callback URL, and paste it into the provider dashboard as the webhook endpoint. Then trigger a test event from Stripe, GitHub, or Shopify, or replay a captured request with curl or Postman to validate the same code path. Watch your logs while you do it, because you need to confirm the payload arrived intact, the signature or headers matched, and the app processed both success and failure cases correctly. For deeper workflows, see local webhook testing, webhook endpoint testing, and how to test webhook integrations.
How to inspect webhook payloads locally
When you debug a webhook, inspect the event name, delivery ID, timestamp, content type, HTTP headers, and raw body first. Stripe and GitHub both rely on signature verification tied to the exact raw body, so any middleware that parses JSON, rewrites whitespace, or changes encoding can break HMAC checks. Keep the raw payload intact until verification passes, then compare the signature or secret against the provider’s expected format.
Tools like Webhook.site, ReqPour, and webhook relay setups can store request history, including headers and payloads, so you can inspect and resend the same request later. If you need to see the exact bytes your app received, log the raw body before parsing it and compare it with the provider’s delivery record.
How to verify webhook signatures in development
Signature verification should happen before your handler trusts the payload. In development, that usually means reading the raw body first, then computing the HMAC with the provider secret and comparing it to the signature in the HTTP headers.
A few common mistakes to avoid:
- parsing JSON before verification
- using the wrong secret for the development environment
- letting middleware modify the raw body
- comparing signatures with a non-constant-time check
Stripe, GitHub, Shopify, and Twilio each format signatures differently, so check the provider docs for the exact header name and verification scheme. The safest pattern is to verify first, then parse and process.
How to replay a webhook request
You can replay a webhook request from a capture tool, provider logs, or a saved raw request file. This is useful when you need to reproduce a bug, compare two deliveries, or test a fix without waiting for another event delivery.
A simple replay workflow looks like this:
- Capture the original request, including headers and raw body.
- Save the payload exactly as received.
- Send it again with
curl, Postman, ReqPour, or a provider replay feature. - Confirm your handler behaves the same way on the second run.
Replay is especially helpful for debugging idempotency and duplicate deliveries, because it lets you verify that the same event does not create duplicate records or side effects.
How to test webhook retries and duplicate deliveries
Providers retry when your endpoint times out, returns an error, or cannot be reached. To test that behavior locally, deliberately return a non-2xx response, delay the handler long enough to trigger a timeout, or disconnect the tunnel and watch the provider retry.
Your handler should be idempotent so duplicate deliveries do not create duplicate orders, messages, or database rows. A common pattern is to store the event ID, check whether it has already been processed, and skip side effects if the same delivery arrives again.
This is where real webhook testing matters more than a mock. A mock webhook can help you verify your code path, but it will not fully reproduce provider retry logic, delivery timing, or signature behavior. Use mocks for unit tests and a real webhook through a tunnel or relay for QA and staging validation.
Best tools and workflows for local webhook testing
Choose the tool based on what you need to prove. If you want to validate real provider behavior end to end, use ngrok to expose localhost and receive actual events from Stripe, GitHub, Shopify, Slack, or Twilio. That gives you the closest match to production because the provider sends the real webhook, signs it, retries it, and expects a real acknowledgment from your app.
If your goal is fast capture and inspection, Webhook.site is the quickest way to see request structure, headers, and payload shape without wiring up a full app. It works well when you need to understand what a provider sends before you build the handler, or when you want a disposable endpoint for checking malformed payloads and header formats.
For teams that want a webhook relay centered on local development and replay workflows, ReqPour is a strong fit. It helps route events into your development environment while keeping the debugging loop tight, which is useful when you need to inspect deliveries, replay requests, and compare behavior across runs. See the broader webhook development tool local testing and webhook relay for local testing guides for setup patterns.
Use a mock webhook when you need deterministic tests for edge cases: bad signatures, malformed JSON, timeout handling, duplicate deliveries, and idempotency checks. Use a real webhook when you need to validate the full path from provider to handler, including signature verification, retry logic, and provider-specific headers. The best workflow usually combines both: mocks for unit and integration tests, real events for QA and staging validation, and a tunnel or relay for local development. For a practical testing checklist, see how to test webhook integrations.
Safest ways to test webhooks during development
The safest approach is to keep production and development separate, use a dedicated development environment secret, and avoid pointing live business logic at a test endpoint. Prefer a tunnel or relay over exposing your machine directly, and use HTTPS whenever a provider requires a public callback URL.
For sensitive systems, test in this order:
- mock the webhook payload for unit tests
- replay captured requests in a controlled environment
- use a tunnel or relay for real provider events
- validate the same handler again in staging before production
That sequence reduces risk while still letting you inspect payloads, verify signatures, and confirm retry behavior.
Conclusion
To test webhooks locally, start with a mock for fast feedback, then move to a tunnel or relay when you need real event delivery from Stripe, GitHub, Shopify, Slack, or Twilio. Use ngrok when you want a quick public HTTPS URL, use Webhook.site when you want simple inspection, and use ReqPour when you need a webhook relay with capture and replay. The key is to preserve the raw body, verify signatures before parsing, and test idempotency and retries before you ship.