Skip to main content

How to Test Webhooks Locally Without Deploying

Forward real webhook requests to localhost through a temporary HTTPS link, then close it when the test is done.

Updated 4 min readMarkdown
Part of seriesGhostly Share

A webhook provider cannot call localhost. Your application can be working perfectly on your machine, but from the provider's point of view, http://localhost:3000 points back to its own infrastructure.

The usual workaround is to deploy every change before testing. That is slow when you are still adjusting payload parsing, signatures, retries, or response codes. A temporary HTTPS link gives you a shorter feedback loop: the provider sends a real request to a public URL, and the request is forwarded to your local server.

This guide builds a small receiver, exposes it only for the test, and closes the link afterward.

1. Create a local webhook receiver

Here is a minimal Express endpoint for inspecting JSON payloads:

const express = require("express");

const app = express();
app.use(express.json());

app.post("/webhooks/demo", (req, res) => {
  console.log("Headers:", req.headers);
  console.log("Payload:", req.body);
  res.sendStatus(204);
});

app.listen(3000, () => {
  console.log("Webhook receiver: http://localhost:3000/webhooks/demo");
});

Install the dependency and start the server:

npm install express
node server.js

Before involving an external service, verify the route locally:

curl -i -X POST http://localhost:3000/webhooks/demo \
  -H "Content-Type: application/json" \
  -d '{"event":"test","message":"hello from localhost"}'

You should receive HTTP/1.1 204 No Content, and the payload should appear in the terminal running the server.

This receiver is intentionally simple. Production webhook handlers should authenticate requests and usually need the unmodified request body for signature verification. Follow the provider's documentation before using the endpoint with real data.

Open Ghostly Share while the local server is running. It should discover the app on port 3000 automatically. Select the app, start a Public share, and copy the generated HTTPS URL.

If you prefer the terminal, the equivalent CLI workflow is:

ghs share 3000 --expires-after 30m --url-only

Keep the desktop app or CLI process running for the duration of the test. The public URL forwards requests to the local origin; it does not deploy or copy the application.

Append the local route to the generated host:

https://<temporary-host>/webhooks/demo

That complete URL is the callback address you enter in the webhook provider's test or sandbox configuration.

3. Send a real test event

Use the provider's Send test event, Redeliver, or equivalent action. Watch the local terminal for the headers and JSON payload, and check the delivery log on the provider side.

A successful round trip should look like this:

  1. The provider sends an HTTPS request to the temporary URL.
  2. The request reaches your local /webhooks/demo route.
  3. Your handler logs the payload and returns 204.
  4. The provider records a successful delivery.

You can now change the handler and send another event immediately. There is no deployment step between iterations.

Common problems

The provider reports 404

Confirm that the public callback includes the complete local path. Sharing port 3000 does not automatically add /webhooks/demo.

The provider times out

Return a successful response quickly, then move slower work to a queue or background process. Many providers retry when a handler takes too long.

The event arrives more than once

Webhook delivery is commonly at least once. Store the provider's event ID and make the real handler idempotent instead of assuming every request is unique.

Signature verification fails

Many signature schemes require the exact raw request body. JSON parsing can change what gets verified, so use the provider's official middleware or raw-body instructions.

Keep the test endpoint small and temporary

A public webhook callback must be reachable without an interactive login page. That means password protection at the tunnel level is often unsuitable for automated providers. Protect the workflow in other ways:

  • use a dedicated test account and test signing secret;
  • verify the provider's signature in your handler;
  • expose only the development service you need, not a database or admin panel;
  • set a short expiration time when possible;
  • avoid real customer data and production credentials;
  • stop the public share as soon as the test is complete.

When you stop sharing or press Ctrl+C, the temporary route no longer serves your local application.

A faster webhook feedback loop

The useful part of local webhook testing is not the tunnel itself. It is the ability to inspect a real request, adjust your code, and retry within seconds. Temporary links keep that loop short without turning an unfinished handler into a permanent deployment.

Ghostly Share is available as a desktop app for Windows and Linux, with a separate CLI for terminal workflows:

About the author

  • Software engineer from Austria building simple tools for developers and power users. Founder of GhostlyInc and creator of projects like Ghostly Bridge and PixelPress. Interested in performance, clean architecture, and practical software.

No comments yet. Be the first to comment.

Ghostly Share

Part 2 of 2

Guides, release notes, and practical workflows for Ghostly Share — discover local web apps and share them over LAN or through secure public links.

Start from the beginningHow to Share Localhost Without Port ForwardingTest on another device, share a demo, or receive webhooks without touching your router.

More from this blog