Help us shape the future of Go! Take the 2026 Go Developer Survey

webhook-relay

module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 6, 2026 License: MIT

README

webhook-relay

Go Reference

Receive webhooks on a public server and handle them on your laptop. A self-hosted alternative to tunnelling services, built for webhook development.

Stripe/GitHub/… ──POST──► relay-server /h/stripe ══websocket══► relay (CLI) ──► http://localhost:3000
                               │  stores every request                          (your app)
                               └─ inspector UI: list, detail, replay, diff

Who it's for

Developers who need to test webhook integrations (payment providers, GitHub, chat bots) against code running locally. Unlike a generic tunnel, every request is recorded. You can look at it later, replay it after fixing your handler, and diff two deliveries to see what changed.

Install

Prebuilt binaries for Linux, macOS and Windows (amd64 and arm64) are on the releases page. Each archive contains the relay-server and relay binaries, the README and the license; checksums.txt has the SHA-256 sums. For example, on Linux amd64:

curl -LO https://github.com/AbdeldjalilChougui/webhook-relay/releases/download/v0.1.0/webhook-relay_0.1.0_linux_amd64.tar.gz
tar xzf webhook-relay_0.1.0_linux_amd64.tar.gz

Or build from source with Go:

go install github.com/AbdeldjalilChougui/webhook-relay/cmd/...@latest

Quick start

On a server with a public URL:

go build -o relay-server ./cmd/relay-server
RELAY_TOKEN=change-me ./relay-server -addr :8080 -db relay.db

On your machine:

go build -o relay ./cmd/relay
RELAY_TOKEN=change-me ./relay -server https://relay.example.com -channel stripe -to http://localhost:3000

Then point the webhook at https://relay.example.com/h/stripe. Anything after the channel is kept: /h/stripe/v1/events?x=1 arrives at http://localhost:3000/v1/events?x=1. The response from your local app goes back to the sender, so signature checks and status-code retries behave as they would in production.

The inspector is at https://relay.example.com/ (HTTP Basic auth: any user name, the token as password).

Server flags
flag default
-addr :8080 listen address (put TLS in front, e.g. Caddy or nginx)
-db relay.db SQLite file
-keep 500 requests kept per channel
-max-body 1048576 bytes stored and forwarded per request
-timeout 25s how long a webhook waits for your local answer

RELAY_TOKEN protects the websocket, the inspector and the JSON API. The /h/… endpoints are public by design.

How it works

  • Capture. /h/{channel}/{rest...} reads the body (up to the limit), strips hop-by-hop headers and stores the request in SQLite before doing anything else. A delivery is never lost, even when no client is listening. In that case the sender gets 202 Accepted with the request ID.
  • Relay. Each channel has at most one connected client, and a newer client replaces the older one. The server sends a JSON request frame over the websocket with a random correlation ID, then waits for the matching response frame or the timeout. The client performs the HTTP call to the local target concurrently, so slow handlers don't block each other. If the target is unreachable, the sender gets a 502 and the error is stored. The server pings every 25 s to keep proxies from dropping the connection. The client reconnects with exponential backoff but stops on 401/403.
  • Inspector. These are server-rendered html/template pages embedded in the binary, with no JavaScript. Replay re-sends a stored request as a new request linked to the original. Diff renders both requests as text (request line, sorted headers, pretty-printed JSON body) and shows a line diff (LCS).
  • API. GET /api/channels/{c}/requests, GET /api/requests/{id}, POST /api/requests/{id}/replay (Bearer token).
  • Storage. It uses the pure-Go modernc.org/sqlite driver (no cgo) in WAL mode with one connection. Old requests are pruned per channel on insert.

Tests

go test -race ./...

The end-to-end tests start the relay server and a real client over a websocket in-process, with an httptest server as the "local app". They cover forwarding (method, path, query, headers, body, status, response headers), storing while no client is connected and then replaying, Bearer and Basic auth, the client giving up on a bad token, invalid channel names, the 502 when the target is down, the inspector pages including the diff output and replay redirect, and a newer client replacing an older one. There are also unit tests for the store (pruning, round trips) and the diff.

Status and limitations

  • One client per channel. There's no fan-out to several developers.
  • A single shared token. There are no per-channel credentials or user accounts.
  • Bodies over -max-body are truncated when stored and when forwarded. Streaming and very large payloads aren't supported, and neither is websocket passthrough (it's for webhooks, not general tunnelling).
  • The server keeps waiting clients in memory, so it runs as a single instance. Scaling horizontally would need a shared pub/sub.
  • No TLS termination built in. Run it behind a reverse proxy.
  • The UI has no pagination beyond the latest 200 requests per channel.

License

MIT. See LICENSE.

Directories

Path Synopsis
cmd
relay command
Command relay connects to a relay server and forwards a channel's webhooks to a local URL.
Command relay connects to a relay server and forwards a channel's webhooks to a local URL.
relay-server command
Command relay-server is the public side: it receives webhooks on /h/{channel}, stores them in SQLite and hands them to connected clients.
Command relay-server is the public side: it receives webhooks on /h/{channel}, stores them in SQLite and hands them to connected clients.
internal
client
Package client is the CLI side: it keeps a websocket open to the relay server, forwards each webhook to a local URL and sends the answer back.
Package client is the CLI side: it keeps a websocket open to the relay server, forwards each webhook to a local URL and sends the answer back.
diff
Package diff computes a line diff with the longest-common-subsequence method.
Package diff computes a line diff with the longest-common-subsequence method.
protocol
Package protocol defines the JSON frames exchanged over the websocket between the relay server and the CLI client.
Package protocol defines the JSON frames exchanged over the websocket between the relay server and the CLI client.
server
Package server is the public relay: it captures webhooks on /h/{channel}, hands them to the connected CLI client over a websocket, returns the client's response, and serves an inspector UI.
Package server is the public relay: it captures webhooks on /h/{channel}, hands them to the connected CLI client over a websocket, returns the client's response, and serves an inspector UI.
store
Package store keeps received webhook requests (and the responses the local target gave) in SQLite.
Package store keeps received webhook requests (and the responses the local target gave) in SQLite.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL