Documentation
Webhook Proxy relays HTTP requests from the internet to your local server and returns the real response to the caller — no port forwarding required.
Overview
When an external service (a payment provider, a CI system, a SaaS webhook) needs to reach your local development server, Webhook Proxy stands in the middle:
- You create a channel — a permanent public URL tied to a WebSocket tunnel.
- Your local process opens a WebSocket to the tunnel endpoint and listens for forwarded requests.
- The external service POSTs (or GETs, PATCHes, …) to your channel URL. The proxy holds the HTTP connection open, forwards the request over the tunnel, waits for your local server's response, and returns it to the caller — including status code, headers, and body.
The channel URL is stable for the lifetime of the session. Reconnecting to the tunnel resumes the same channel.
Quickstart
1. Create a channel
Click Create a channel on the home page, or navigate to /new. You will be redirected to a unique channel page like /abc1234xyz.
2. Connect your local server
Download the reference TypeScript client from the link shown on the channel page, then run it:
CHANNEL_URL=wss://webhook-proxy.manuelt.de/abc1234xyz/_tunnel \
TARGET=http://localhost:3000 \
npx tsx client.ts
The client will print Tunnel connected. and the status badge in the UI will turn green.
3. Point your webhook source at the channel URL
Use the channel URL shown in the sidebar (e.g. https://webhook-proxy.manuelt.de/abc1234xyz) as the webhook destination in the external service. All HTTP methods and sub-paths are supported — /abc1234xyz/events/push works the same as /abc1234xyz.
4. Inspect requests in real time
Incoming requests appear in the request list as they arrive. Click one to see full request and response headers and bodies. Use ↺ Replay to re-send any past request through the tunnel.
Channel UI
The channel page is split into three panes:
- Sidebar (left) — channel URL, tunnel WebSocket URL, configuration controls (auth, auto-respond, test request).
- Request list (middle) — live feed of all relayed requests with method, path, status, and timestamp. Click to select.
- Detail panel (right) — full inspection of the selected request: method, path, status, duration, request headers, request body, response headers, response body. Replay and copy-as-curl actions.
The tunnel badge in the top-right of the header reflects the current connection state of the tunnel WebSocket in real time.
Auth Header
You can require an arbitrary HTTP header on all incoming requests. Enable Require auth header in the sidebar, enter the header name and expected value, and click Save.
Requests that are missing the header or provide the wrong value will receive a 401 response immediately — they will not be forwarded to the tunnel.
The auth setting lives in server memory and resets when the server restarts. It is per-channel and takes effect immediately after saving.
Auto-respond
Enable Reply 204 No Content to make the proxy immediately return 204 to every incoming request without forwarding it to the tunnel. This is useful for silencing a webhook source while you are not actively debugging.
Requests are still logged in the request list so you can see what came in.
Test Requests
The Test Request panel in the sidebar lets you send an artificial request through the tunnel directly from the browser — no external service needed.
- Choose an HTTP method from the dropdown.
- Enter a path (defaults to
/). - For methods that carry a body (POST, PUT, PATCH, DELETE), a body textarea appears. If you paste valid JSON it is highlighted and
content-type: application/jsonis added automatically. - Click Send. The request appears in the list and is forwarded to your local server.
TypeScript Client
The reference client is a single-file TypeScript module with no external dependencies (requires Node.js 22+). Download it from the /_client.ts link on any channel page.
Programmatic usage
import { WebhookProxy } from "./client.ts";
const proxy = new WebhookProxy({
source: "wss://webhook-proxy.manuelt.de/abc1234xyz/_tunnel",
target: "http://localhost:3000",
});
proxy.start();
// Later, to disconnect:
proxy.close();
Constructor options
| Option | Type | Default | Description |
|---|---|---|---|
| source | string | required | The tunnel WebSocket URL — found on the channel page under Tunnel WebSocket. Always ends with /_tunnel. |
| target | string | required | Base URL of your local server (e.g. http://localhost:3000). All request paths are resolved relative to this URL. |
| reconnectDelay | number | 3000 | Milliseconds to wait before reconnecting after an unexpected disconnect. Set to a higher value if you want less aggressive reconnection. |
Methods
| Method | Description |
|---|---|
| start() | Opens the tunnel WebSocket and begins forwarding requests. Automatically reconnects on disconnect until close() is called. |
| close() | Closes the WebSocket and stops reconnecting. |
CLI usage
The client can be run directly as a CLI tool using environment variables:
CHANNEL_URL=wss://webhook-proxy.manuelt.de/abc1234xyz/_tunnel \
TARGET=http://localhost:3000 \
npx tsx client.ts
| Variable | Required | Description |
|---|---|---|
| CHANNEL_URL | yes | Tunnel WebSocket URL (same as the source option). |
| TARGET | no | Local server base URL. Defaults to http://localhost:3000. |
The process handles SIGINT and SIGTERM for clean shutdown (Ctrl-C works as expected).
How the client forwards requests
For each request received over the WebSocket, the client:
- Resolves the request path against
target. - Strips hop-by-hop headers (
host,connection,transfer-encoding) and recalculatescontent-lengthfrom the decoded body. - Makes a standard Node.js
http/httpsrequest to your local server. - Encodes the response (status, headers, base64 body) and sends it back over the WebSocket so the proxy can return it to the original caller.
- If the local request fails, returns a 502 with a JSON error body rather than leaving the caller hanging.
Protocol
The tunnel is a plain WebSocket connection. You can implement a client in any language that supports WebSockets.
Tunnel WebSocket endpoint
wss://<host>/<channelId>/_tunnel
Only one tunnel connection per channel is active at a time. Opening a second connection closes the first.
Message format
All messages are JSON. The server sends request messages; the client sends back response messages.
Incoming (server → client):
{
"type": "request",
"id": "unique-request-id",
"method": "POST",
"path": "/events/push",
"headers": { "content-type": "application/json", ... },
"body": "<base64-encoded body, or null>"
}
Outgoing (client → server):
{
"type": "response",
"id": "unique-request-id", // must match the request id
"status": 200,
"headers": { "content-type": "application/json", ... },
"body": "<base64-encoded body, or null>"
}
Request bodies are base64-encoded to safely transport binary payloads. If there is no body, body is null.
Timeouts and error responses
If the proxy does not receive a matching response within 30 seconds it returns 504 to the original caller. If no tunnel is connected when a request arrives, the caller receives 503 immediately.
UI WebSocket endpoint
wss://<host>/<channelId>/_ui
The channel page connects here to receive live events. The server pushes tunnel_status, request, and response messages as they happen. This endpoint is read-only — the UI sends no messages.