Gosmee is a webhook relayer that runs anywhere with ease. It also serves as a GitHub Hooks replayer using the GitHub API.
Gosmee enables you to relay webhooks from itself (as a server) or from https://smee.io to your local laptop or infrastructure hidden from the public internet.
It makes exposing services on your local network (like localhost) or behind a VPN quite straightforward. This allows public services, such as GitHub, to push webhooks directly to your local environment.
Here's how it works:
This creates a proper bridge between GitHub webhooks and your local development environment.
Alternatively, if you'd rather not use a relay server, you can use the GitHub API to replay webhook deliveries directly. (beta)
For those who prefer a visual explanation of how gosmee works:

sequenceDiagram
participant SP as Service Provider (e.g., GitHub)
participant GS as Gosmee Server (Public URL / smee.io)
participant GC as Gosmee Client (Local / Private Network)
participant LS as Local Service (e.g., localhost:3000)
Note over GC, LS: Runs in private network/local machine
Note over SP, GS: Accessible on the public internet
GC->>+GS: 1. Connect & Listen via SSE
SP->>+GS: 2. Event triggers -> Sends Webhook Payload (HTTP POST)
GS->>-GC: 3. Relays Webhook Payload (via SSE connection)
GC->>+LS: 4. Forwards Webhook Payload (HTTP POST)
LS-->>-GC: 5. (Optional) HTTP Response
GS-->>-SP: 6. (Optional) HTTP Response (e.g., 200 OK)
Learn more about the background and features of this project in this blog post: https://blog.chmouel.com/posts/gosmee-webhook-forwarder-relayer

The web interface of the gosmee server features a live event feed that shows webhook events in real-time:
Each event in the feed shows:
Please visit the release page and choose the appropriate archive or package for your platform.
brew tap chmouel/gosmee https://github.com/chmouel/gosmee
brew install gosmee
yay -S gosmee-bin
docker run ghcr.io/chmouel/gosmee:latest
docker run -d -p 3026:3026 --restart always --name example.org ghcr.io/chmouel/gosmee:latest server --port 3026 --address 0.0.0.0 --public-url https://example.org
go install -v github.com/chmouel/gosmee@latest
Clone the repository and use:
-$ make build
-$ ./bin/gosmee --help
Gosmee is available from nixpkgs.
nix-env -iA gosmee
nix run nixpkgs#gosmee -- --help # your args are here
System service example files for macOS and Linux are available in the misc directory.
You can deploy gosmee on Kubernetes to relay webhooks to your internal services.
Two deployment configurations are available:
The server deployment exposes a public webhook endpoint to receive incoming webhook events:
kubectl apply -f misc/gosmee-server-deployment.yaml
Key configuration:
--public-url to your actual domain where the service will be exposed--webhook-signature and --allowed-ips optionsThe client deployment connects to a gosmee server (either your own or smee.io) and forwards webhook events to internal services:
kubectl apply -f misc/gosmee-client-deployment.yaml
Key configuration:
http://service.namespace:8080)--saveDir flag enables saving webhook payloads to /tmp/save for later inspectionFor detailed configuration options, please refer to the documentation comments in each deployment file.
Shell completions are available for gosmee:
# BASH
source <(gosmee completion bash)
# ZSH
source <(gosmee completion zsh)
If you plan to use the https://smee.io service, you can generate your own smee URL by visiting https://smee.io/new.
If you want to use the https://hook.pipelinesascode.com service then you can directly generate a URL with the -u / --new-url flag.
Once you have the relay URL, the basic usage is:
gosmee client https://smee.io/aBcDeF https://localhost:8080
This command will relay all payloads received by the smee URL to a service running on http://localhost:8080.
You can also save all relays as shell scripts for easy replay:
gosmee client --saveDir /tmp/savedreplay https://smee.io/aBcDeF https://localhost:8080
This command saves the JSON data of new payloads to /tmp/savedreplay/timestamp.json and creates shell scripts with cURL options at /tmp/savedreplay/timestamp.sh. Replay webhooks easily by running these scripts.
You can configure the SSE client buffer size (in bytes) with the --sse-buffer-size flag. The default is 1048576 (1MB).
Protected channels are optional and only apply to channel IDs listed in the server's --encrypted-channels-file.
Plaintext gosmee channels still work without a key file:
gosmee client https://myserverurl/plain-channel https://localhost:8080
When connecting to a protected channel on your own gosmee server, the client must use a pre-generated keypair file. There is no client-side auto-generation during gosmee client startup.
Generate a keypair once:
gosmee keygen --key-file ~/.config/gosmee/client-key.json
This writes the private key file and prints the corresponding public key to stdout. Add that public key to the server's protected-channel config.
Then connect with the key file:
gosmee client --encryption-key-file ~/.config/gosmee/client-key.json https://myserverurl/CHANNEL_ID https://localhost:8080
Notes:
https://smee.io does not use client keys.--encrypted-channels-file, --encryption-key-file is not needed and payloads stay plaintext.--saveDir are written after decryption on the client side.For those who prefer HTTPie over cURL, you can generate HTTPie-based replay scripts:
gosmee client --httpie --saveDir /tmp/savedreplay https://smee.io/aBcDeF https://localhost:8080
This will create replay scripts that use the http command instead of curl. The generated scripts support the same features as cURL scripts; the output will be rather nicer and presented in colour.
You can ignore certain events (identified by GitLab/GitHub/Bitbucket) with one or more --ignore-event flags.
If you only want to save payloads without replaying them, use --noReplay.
By default, you'll get colourful output unless you specify --nocolor.
Output logs as JSON with --output json (which implies --nocolor).
You can execute a shell command whenever a webhook event is received using --exec:
gosmee client --exec 'jq . $GOSMEE_PAYLOAD_FILE' https://smee.io/aBcDeF http://localhost:8080
The payload and headers are written to temporary files (automatically cleaned up after the command finishes). The following environment variables are set:
| Variable | Description |
|---|---|
GOSMEE_EVENT_TYPE |
The event type (e.g., push, pull_request) |
GOSMEE_EVENT_ID |
The delivery ID |
GOSMEE_CONTENT_TYPE |
The content type of the payload |
GOSMEE_TIMESTAMP |
The timestamp of the event |
GOSMEE_PAYLOAD_FILE |
Path to a temporary file containing the JSON payload body |
GOSMEE_HEADERS_FILE |
Path to a temporary file containing the webhook headers as JSON |
To only run the command for specific event types, use --exec-on-events:
gosmee client --exec './handle-push.sh' --exec-on-events push --exec-on-events pull_request https://smee.io/aBcDeF http://localhost:8080
By default, --exec runs with a minimal, safe environment (for example PATH, HOME, and locale-related variables), not the full gosmee process environment. To pass additional variables through, use --exec-env-vars VAR_NAME (repeat the flag for multiple names), or set GOSMEE_EXEC_ENV_VARS as a comma-separated list.
The --exec command runs synchronously after the webhook is forwarded to the target URL (if replay is enabled). A slow command will delay processing of subsequent events. If you need asynchronous execution, background your command (e.g., --exec './my-script.sh &'). A non-zero exit code is logged as an error but does not stop processing further events.
Both --exec and --exec-on-events also work with the replay command.
Security Warning: The
--execflag runs arbitrary shell commands with the webhook payload available via$GOSMEE_PAYLOAD_FILE. When receiving webhooks from untrusted sources, a malicious payload could exploit a naively written script (e.g., one that passes unsanitized fields to shell commands). Always validate and sanitize webhook payloads in your exec scripts. Consider using--webhook-signatureon the server side to verify webhook authenticity.
Both cURL and HTTPie replay scripts include these command-line options:
-l, --local: Use local debug URL-t, --target URL: Specify target URL directly-h, --help: Show help message-v, --verbose: Enable verbose outputExamples:
# Use local debug endpoint
./timestamp.sh -l
# Specify custom target URL
./timestamp.sh -t http://custom-service:8080
# Use verbose mode for debugging
./timestamp.sh -v
# Show help
./timestamp.sh -h
Scripts also respect the GOSMEE_DEBUG_SERVICE environment variable for alternative target URLs.
With gosmee server you can run your own relay server instead of using https://smee.io.
By default, gosmee server binds to localhost on port 3333. For practical use, you'll want to expose it to your public IP or behind a proxy using the --address and --port flags.
For security, you can use Let's Encrypt certificates with the --tls-cert and --tls-key flags.
There are many flags available - check them with gosmee server --help.
To use your server in normal plaintext mode, access it with a URL format like:
The random ID must be 12 characters long with characters from a-zA-Z0-9_-.
Generate a random ID easily with the /new endpoint:
% curl http://localhost:3333/new
http://localhost:3333/NqybHcEi
If you want specific channels to be key-protected, provide --encrypted-channels-file. Only the channels listed in that file require authorized client keys and encrypted SSE delivery. All other gosmee channels continue to work in legacy plaintext mode.
Example protected-channel config:
{
"channels": {
"customer-a-channel": {
"allowed_public_keys": [
"CLIENT_PUBLIC_KEY_1",
"CLIENT_PUBLIC_KEY_2"
]
}
}
}
Start the server with that config:
gosmee server --encrypted-channels-file /etc/gosmee/channels.json --public-url https://myserverurl
For a protected channel, configure the webhook to post to:
https://myserverurl/customer-a-channel
Important:
--encrypted-channels-file are protected./new remain available for plaintext channels, but protected channels are not exposed through the browser UI.Caddy is rather ideal for running gosmee server:
https://webhook.mydomain {
reverse_proxy http://127.0.0.1:3333 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
}
It automatically configures Let's Encrypt certificates for you.
Running gosmee server behind nginx requires some configuration:
```nginx
$ claude mcp add gosmee \
-- python -m otcore.mcp_server <graph>