Local development

Receive real webhooks on your local machine — no public URL, no ngrok, no firewall rules.

✦Similar to the Stripe CLI, the HostWebhook CLI creates a secure tunnel from our servers to your localhost. The difference: it works with any webhook source — Stripe, GitHub, Shopify, or your own services.

How it works

The CLI opens a persistent WebSocket connection to HostWebhook. Incoming webhooks are held on our side, sent to the CLI over that connection, and the CLI forwards them as a regular HTTP POST to your local server. The response travels back the same way, so HostWebhook records the real status code and latency.

All retry logic, signature verification, and event history work exactly as in production — the only difference is the last hop goes to localhost instead of a public URL.


Option A — Ephemeral tunnel (fastest)

No setup required. hostwh forward creates a temporary webhook, gives you an ingress URL, and deletes the webhook automatically when you press Ctrl+C.

⚠New webhooks now start paused. hostwh 1.4.1 and earlier create the forward webhook paused, so nothing arrives. Update the CLI with npm install -g hostwh: newer versions create it active.
1

Install the CLI and log in

npm install -g hostwh
hostwh login
2

Start the tunnel

hostwh forward http://localhost:3001/webhooks

You'll immediately see a unique ingress URL:

  Creating temporary webhook...

  Ingress URL  https://api.hostwebhook.com/in/whk_abc123...

  Copy this URL into Stripe, GitHub, or any webhook provider.

  Tunnel active → http://localhost:3001/webhooks  press Ctrl+C to stop and delete
3

Paste the ingress URL into your webhook source

Copy the ingress URL and configure it in Stripe, GitHub, or any service sending you events. Webhooks will start arriving in your terminal immediately.

4

Stop the tunnel — webhook is auto-deleted

Press Ctrl+C. The CLI deletes the temporary webhook automatically — nothing left to clean up.

ℹIf the process is killed forcefully (e.g. kill -9), the webhook remains under the name forward-<8chars> and can be deleted with hostwh delete <id> or from the dashboard.

forward options

FlagDescription
--show-payloadPrint the full JSON payload for each event
--show-headersPrint forwarded request headers

Option B — Persistent tunnel

Use hostwh listen --forward when you want to reuse the same ingress URL across sessions — for example, a Stripe webhook you configured once and don't want to update.

1

Install the CLI and log in

npm install -g hostwh
hostwh login

Verify the installation with hostwh --version.

2

Create a webhook

hostwh create

The CLI prompts for each field, then prints the Ingress URL:

  Name: my-app
  Target URL: https://myapp.com/webhooks
  Description (optional):
  Max retries [3]:

  ✔ Webhook created  a1b2c3d4

  Ingress URL  https://api.hostwebhook.com/in/<token>

  It starts paused: its URL rejects every event until you activate it in the dashboard.
  To start forwarding: hostwh listen a1b2c3d4 --forward http://localhost:3001

A webhook made with hostwh create starts paused, like any new webhook: press Activate on it in the dashboard before you send it traffic.

ℹSet the Target URL to your production address. While the tunnel is active, all traffic is intercepted by the CLI and sent to --forward instead. When you disconnect, traffic resumes to the Target URL automatically.
3

Give the ingress URL to your webhook source

Paste the ingress URL in the webhook settings of Stripe, GitHub, or any other provider. You only do this once — the same URL works in both local development and production.

4

Start the tunnel

hostwh listen <webhook-id> --forward http://localhost:3001/webhooks
  HostWebhook  my-app  a1b2c3d4
  Tunnel active → http://localhost:3001/webhooks  press Ctrl+C to stop

  12:34:01  tunnel  a1b2c3d4  http://localhost:3001/webhooks  HTTP 200  143ms
5

Trigger a test event

macOS / Linux

curl -X POST https://api.hostwebhook.com/in/<token> \
  -H "Content-Type: application/json" \
  -d '{"event": "test", "data": {"hello": "world"}}'

Windows (PowerShell)

Invoke-WebRequest -UseBasicParsing `
  -Uri "https://api.hostwebhook.com/in/<token>" `
  -Method POST `
  -Headers @{"Content-Type"="application/json"} `
  -Body '{"event": "test", "data": {"hello": "world"}}'

A successful response is 202 Accepted. Check the CLI output and your local server — both should show the request.

listen --forward options

FlagDescription
-f, --forward <url>Forward events to this local URL
--show-payloadPrint the full JSON payload for each event
--show-headersPrint forwarded request headers

Observe mode (no forwarding)

Watch events in real-time without forwarding them anywhere — useful for debugging or monitoring a staging environment:

hostwh listen <webhook-id>

Going to production

When you're ready to deploy, just stop the CLI. HostWebhook will automatically fall back to delivering webhooks directly to the Target URL configured on your webhook. No code changes, no configuration updates — the ingress URL you gave to Stripe or GitHub stays the same.

✦The tunnel is exclusive: while the CLI is connected, 100% of traffic goes to your local machine. As soon as you disconnect, traffic resumes to the production Target URL with normal retry behavior.

Verifying signatures locally

The CLI forwards the real X-Webhook-Signature header, so your signature verification code works exactly as it will in production. You can test your HMAC logic against real payloads without any mocking. See Verifying signatures for implementation examples.


A local server behind a public URL

hostwh expose is the other kind of tunnel: instead of bringing webhooks to your machine, it gives a server on your machine a public URL — for an AI Node's tunnel tools, a Voice Agent's tunnel section, or a self-hosted model. Pass --id to keep the same URL across restarts:

hostwh expose http://localhost:3000 --id my-app

The CLI prints the public URL, https://hwtunnels.com/t/<tunnel-id>/. A request to it goes through the CLI to your server, and your server's answer comes back with its status and body — except a 502: Cloudflare, in front of hwtunnels.com, replaces it with its own page, so answer 503 if whoever calls needs to read the body. Set-Cookie is dropped, and the answer carries Content-Disposition: attachment, a sandbox Content-Security-Policy and X-Content-Type-Options: nosniff; the bytes don't change, so they only matter in a browser.

When the tunnel can't answer

When the request can't get an answer from your server, the tunnel answers 503 with JSON — { "error": "…", "code": "…" } — and Cache-Control: no-store. Not a 502: Cloudflare, in front of hwtunnels.com, replaces a 502 with its own page, and the reason would never reach whoever called. Read code; error is for people and its wording can change.

codeWhen
tunnel_offlineNo CLI is connected to the tunnel. Start hostwh expose again.
tunnel_disconnectedThe CLI disconnected before your server's answer came back.
tunnel_timeoutNothing came back: 60 seconds in all, or 30 seconds without any data. If your server had already started answering, the answer is cut short instead.
tunnel_request_failedThe CLI couldn't get an answer from your server and says why in error. With your server stopped, hostwh 1.4.1 says: Local server not responding at http://localhost:3000. Is it running?

A 500 with tunnel_proxy_failed is different: the failure was in HostWebhook, not in the tunnel or your server.