Open-source project
tarampampam/webhook-tester avatar
tarampampam/webhook-tester

webhook-tester: a self-hosted endpoint for capturing and shaping webhook requests

🔭 Powerful tool for testing WebHooks and more

548 stars59 forksGoMIT

At a glance

What is it?
webhook-tester is a Go application that hands you a unique URL, records every request that arrives on it and lets you control the response code, headers, body and delay, so you can develop and debug webhook integrations without a third party service. It ships a React UI compiled into the binary, supports memory, Redis and filesystem storage, has built-in WebSocket notifications, and can expose a local instance to the internet through an ngrok tunnel.
Who is it for?
webhook-tester fits a developer who needs a webhook receiver to point at while an integration is being built, especially one who cannot send payloads to a third party service, and it fits a team that already runs Redis and wants recorded requests to survive a restart. It does not fit a shared public instance that needs durable sessions, because the memory driver clears everything on shutdown and the auto-created session mode exists to work around exactly that.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What webhook-tester does with the request that arrives

The application lets you test and debug webhooks and HTTP requests using unique, randomly generated URLs. Each session gets its own URL, requests that arrive are recorded, and the response side is fully under your control: you can customize the response code, the Content-Type HTTP header, the response content, and even set a delay for responses.

That last capability is the one that makes it more than a request viewer. A webhook receiver that always answers instantly with 200 teaches you nothing about your integration, and plenty of real systems are not like that. A receiver that takes four seconds, or that answers 503 on the first call and 200 on the second, or that returns a body your parser has to cope with, is a different thing to develop against, and this tool lets you reproduce that behaviour on demand.

The project positions itself explicitly as a free and self-hosted alternative to webhook.site and requestinspector.com and similar services. That framing explains the design decisions. A hosted service gives you a URL on the public internet, which is exactly what you need when the sender is a third party such as GitHub, GitLab or Bitbucket, and this tool reproduces that capability through a tunnel rather than by being hosted itself.

The application is written in Go for high performance, and it includes a lightweight UI written in ReactJS that is compiled into the binary, so no additional assets are required at runtime. There is WebSocket support for real-time webhook notifications in the UI, and the description notes you need no third party services like pusher.com for that.

The recorded requests are viewable in the UI in a binary view as well as the usual form, and the tool supports both JSON and human-readable logging formats. It is MIT licensed, the last push was on 2026-10-11, and the recent releases are v2.2.6 from 2026-02-24, v2.2.7 from 2026-04-10 and v2.3.0 from 2026-04-13.

Installing it as a single binary, and the Docker route

Installation is either one downloaded binary or one container.

For a binary, the documentation points at the releases page, and the example given is for an amd64 system such as Debian or Ubuntu:

shell
curl -SsL -o ./webhook-tester https://github.com/tarampampam/webhook-tester/releases/latest/download/webhook-tester-linux-amd64
chmod +x ./webhook-tester
./webhook-tester start

Each release includes binaries for linux, darwin and windows, on amd64 and arm64, and the description makes the point that all you need is to download and run a single binary file. The `start` subcommand is what launches the server, and the help output is generated into the project description by a documentation generator, which means the flag list in the README is produced from the code rather than written by hand.

The Docker route uses either the GitHub Container Registry image or the Docker Hub mirror, and the shortest run command is:

shell
docker run --rm -t -p "8080:8080/tcp" ghcr.io/tarampampam/webhook-tester:2

That command starts the app with the default configuration on port 8080, and the note explains the port mapping: the first port in the -p argument is the host port and the second is the application port inside the container. You then open the browser at localhost:8080, and you stop it with Ctrl+C in the terminal where it runs.

There is a specific warning about tags. The documentation recommends avoiding the latest tag because major upgrades may include breaking changes, and instead using specific tags in X.Y.Z format for version consistency. That is a sensible instruction for a tool whose sessions are referenced by URL in third party dashboards, because an unattended pull of a new major version can change behaviour under you.

For Kubernetes, the project points at a Helm chart published on ArtifactHUB, and the repository has a `deployments/` directory and a `compose.yml` for local development.

The three storage drivers and the two pub/sub drivers

The app supports three storage drivers, configured with the `--storage-driver` flag, and the choice is the main operational decision you make.

The memory driver is described as ideal for local debugging when persistent storage is not needed, and the recorded requests are cleared upon app shutdown. That is the zero-dependency default and the reason the tool can claim standalone operation with in-memory storage and pub/sub and no third party dependencies.

The Redis driver retains data across app restarts, which suits environments where data persistence is required, and the documentation also notes Redis is necessary when running multiple instances behind a load balancer. That second point is the important one, because a load balancer distributing requests across instances means the instance that receives request 2 may not be the one holding session 1, and a shared store is the only way to make that work.

The fs driver keeps all the data in the local filesystem and is useful when you need to store data between app restarts. It is the middle ground: durable like Redis, but without a service to run, and it is the right pick for a single instance on a server with disk you control.

Pub/sub is a separate axis, with two drivers, memory and Redis, configured with the `--pubsub-driver` flag, and this one exists to carry WebSocket notifications. When running multiple instances of the app, the Redis driver is required, for the same reason as above: a WebSocket client connected to instance 1 has to hear about a request that instance 2 received.

The dependency list in the module file confirms the shape of it. The Redis client is github.com/redis/go-redis/v9, the WebSocket implementation is gorilla/websocket, and there is an in-process Redis implementation, alicebob/miniredis/v2, which is a test dependency used to exercise the Redis code path without a Redis server. The CLI is urfave/cli/v3 with a companion urfave/cli-docs/v3 that generates the documentation, the UUID library is google/uuid, the logger is go.uber.org/zap, and the tunnel library is golang.ngrok.com/ngrok.

Tunneling to the internet, and the static session problem

To capture requests from the global internet, the tool uses an ngrok tunnel driver. You enable it with `--tunnel-driver=ngrok` and provide the ngrok authentication token with `--ngrok-auth-token`, and the app then creates the tunnel for you automatically, with no need to install or run ngrok yourself, even inside Docker. With that public URL you can test webhooks from external services such as GitHub, GitLab and Bitbucket.

The FAQ then answers the question that anyone building a webhook consumer eventually hits, which is whether you can have pre-defined static webhook URLs so that a request is captured even without data persistence. The answer is yes, using the `--auto-create-sessions` flag or setting the `AUTO_CREATE_SESSIONS=true` environment variable. The explanation of how it works is elegant: in v1 you had to define sessions during app startup to enable this, but since v2 all you need is to enable the feature, and if the incoming request contains a UUID-formatted prefix, such as http://app/11111111-2222-3333-4444-555555555555/..., a session for that request is created automatically. You then open the session in the UI at http://app/s/11111111-2222-3333-4444-555555555555.

That is the design decision worth understanding, because it inverts the usual model. Instead of the tool handing you a random session id that you then paste into a third party dashboard, the tool derives the session from the URL the third party already has. You can therefore register a fixed UUID URL with a provider, and the receiver will always have somewhere to put the request, even on an instance that keeps nothing in memory between requests.

The demo instance at wh.tarampamp.am is worth a mention because the documentation is honest about it. The tip says it is quite limited, does not persist data, and may be unavailable sometimes. The homepage link in the project description also carries the note that it is just a demo.

Operational details: health checks, probes and the single binary image

The feature list has a cluster of entries that are about running the thing rather than using it, and they are the ones that separate a demo from a deployable service.

There is a CLI health check sub-command included, and liveness probes through a `/healthz` endpoint. The Docker image is multi-architecture and based on `scratch`, which means it contains nothing but the statically linked binary, so there is no shell in the container and no package manager to exploit. It runs as an unprivileged user in Docker.

The repository's own compose file shows how the author runs it in development, and it is instructive. There is an `app` service that is the common use case of running a shell or executing commands, with the source mounted at /src and a `no-new-privileges:true` security option. The `app-http` service runs the Go server with `--use-live-frontend --auto-create-sessions --max-requests 16`, publishes port 8080, and has a healthcheck that wgets the `/healthz` endpoint. The `app-web-dist` service runs `npm run watch` as the node user and waits for the dist robots.txt to appear, and `app-web-serve` runs the Vite dev server with a `DEV_SERVER_PROXY_TO` environment variable pointing at the Go API, published on host port 8081.

That last one is a detail about how the frontend talks to the backend in development, and it explains the split between a live frontend and the compiled one baked into the binary.

The Makefile gives the development workflow. `make install` installs dependencies, `make generate` runs code generation for both Go and the frontend, `make node-build` builds the frontend, `make lint` runs golangci-lint on the host and the frontend linter in a container, and `make e2e` runs a k6 script from the tests directory. The `help` target prints the available commands with descriptions, and the shell targets run a container as your own uid and gid.

There is also an `api/` directory in the tree and an OpenAPI runtime dependency, github.com/oapi-codegen/runtime, which points to a generated API contract.

Where webhook-tester is the wrong choice

The first limit is the public hosting question. This tool is designed to be run by you, so if your webhook sender cannot reach your machine and you do not want to use a tunnel, you need a hosted instance, and the demo is explicitly limited, does not persist data and may be unavailable. That is the gap the project is honest about.

The second limit is that it is a receiver, not a sender. It captures requests and shapes responses. It does not sign payloads, does not retry failed deliveries, does not queue or replay, and does not verify anything about the sender. If your problem is that deliveries are not arriving, or that you need to replay one, this is the wrong tool.

The third limit is scale, and it is a design limit rather than a missing feature. The memory driver clears on shutdown, the fs driver is local to one machine, and the multi-instance story requires Redis for both storage and pub/sub. There is no database, no query language and no retention policy beyond what the driver gives you. The tool is built for development and debugging sessions, and if you want to keep a searchable history of every webhook your platform receives for a year, you want something else and this is a good way to prototype it.

The fourth is the Go version floor. The module file declares go 1.26, and the Dockerfile copies the Go toolchain from a golang:1.26-alpine image, so building from source needs a current toolchain. The frontend build similarly expects a recent Node, since the builder stage uses node:25-alpine.

Compare that with a hosted service such as webhook.site. The difference in approach is that the hosted service gives you a public URL with no tunnel, no storage configuration and no container, and you get no control over the data, the retention or the response behaviour, while this tool gives you a self-hosted instance you can put behind your own auth, keep on your own network, and shape responses exactly, at the cost of running it and dealing with a tunnel when the sender is external.

Licence, project structure and what to check before you deploy it

The project is MIT licensed, with the LICENSE file in the repository root, which is one of the most permissive terms and has no copyleft obligation for a deployment you host yourself. Read the LICENSE file yourself for the exact wording.

The repository layout follows the standard Go project shape with the frontend as a subdirectory. There is `cmd/` for the command line entry points, `internal/` for the application internals, `api/` for the API definition, `web/` for the React frontend, `tests/` for the end to end tests including the k6 script, and `deployments/` for the Kubernetes manifests. The `Dockerfile` is a multi-stage build starting from node:25-alpine with the Go toolchain copied in from golang:1.26-alpine, and it uses build mounts to cache the Go modules and the npm packages separately from the source. The `compose.yml` is the development compose file, and there is a `Makefile`, a `.golangci.yml` for the Go linter, an `AGENTS.md`, a `tools.go.mod` and `tools.go.sum` for the tools-only dependencies, and a `go.mod` and `go.sum`.

The module path is gh.tarampamp.am/webhook-tester/v2, and the v2 in the path is a deliberate signal that this is the second generation of the tool. The FAQ mentions the v1 to v2 change to session handling, and the image tag in the quick start is also `2`.

What to check before you deploy it is a short list. Decide on the storage driver first, because it determines whether your recorded requests survive a restart, and pair it with the pub/sub driver, which must be Redis if you run more than one instance. Then decide whether you need the auto-create-sessions behaviour, since that is what lets a provider send to a fixed UUID URL.

If you are exposing the instance to the internet, treat the tunnel and the session URLs as public, because anyone who knows a session URL can read the requests recorded under it. And pin a specific image tag in X.Y.Z format rather than tracking latest, because the documentation warns that major upgrades may include breaking changes, and pin the Go toolchain version in your build if you compile from source.

Editorial conclusion

webhook-tester fits a developer who needs a webhook receiver to point at while an integration is being built, especially one who cannot send payloads to a third party service, and it fits a team that already runs Redis and wants recorded requests to survive a restart. It does not fit a shared public instance that needs durable sessions, because the memory driver clears everything on shutdown and the auto-created session mode exists to work around exactly that. Before you deploy it, pick a storage driver and a pub/sub driver that match whether you run one instance or several, use the Redis driver if you put more than one instance behind a load balancer, and pin a specific X.Y.Z image tag rather than the latest tag, because major upgrades may carry breaking changes.

Frequently asked questions

What is webhook-tester and what does it do?

It is a Go application for testing and debugging webhooks and HTTP requests using unique, randomly generated URLs. You can customize the response code, the Content-Type header, the response content and the response delay, and it presents itself as a free and self-hosted alternative to services like webhook.site.

How do I install webhook-tester?

Download the binary for your architecture from the releases page and run it, for example `curl -SsL -o ./webhook-tester` from the release download URL, `chmod +x ./webhook-tester` and `./webhook-tester start`. Releases include linux, darwin and windows builds for amd64 and arm64. You can also run the container with `docker run --rm -t -p "8080:8080/tcp" ghcr.io/tarampampam/webhook-tester:2`.

What storage drivers does webhook-tester support?

Three, selected with the `--storage-driver` flag. The memory driver clears recorded requests on shutdown, the Redis driver retains data across restarts and is needed when running multiple instances behind a load balancer, and the fs driver keeps everything on the local filesystem. Pub/sub for WebSocket notifications has its own `--pubsub-driver` flag with memory and Redis options, and Redis is required there for multiple instances.

How do I capture webhooks from an external service?

Enable the ngrok tunnel driver with `--tunnel-driver=ngrok` and pass your token with `--ngrok-auth-token`, and the app creates the tunnel itself with no separate ngrok installation. The resulting public URL can be registered with services such as GitHub, GitLab and Bitbucket. You can also use `--auto-create-sessions` or `AUTO_CREATE_SESSIONS=true` so that requests to a UUID-formatted URL create a session automatically.

What licence is webhook-tester released under?

The project is released under the MIT licence, with the licence text in the LICENSE file at the repository root. The recent releases are v2.2.6 from 2026-02-24, v2.2.7 from 2026-04-10 and v2.3.0 from 2026-04-13, and the last push was on 2026-10-11.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. tarampampam/webhook-tester on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tarampampam-webhook-tester.svg)](https://hysenlabs.com/projects/tarampampam-webhook-tester)

Was this useful?