Skip to content

chmouel/gosmee

Repository files navigation

gosmee - A webhook forwarder, relayer, and replayer

gosmee logo

Gosmee is a webhook relayer that runs anywhere with ease. It also serves as a GitHub Hooks replayer using the GitHub API.

Description

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:

  1. Configure your webhook to send events to a https://smee.io/ URL or to your publicly accessible Gosmee server.
  2. Run the Gosmee client on your local machine to fetch these events and forward them to your local service.

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)

Diagram

For those who prefer a visual explanation of how gosmee works:

Simple

diagram

Detailed

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)
Loading

Blog Post

Learn more about the background and features of this project in this blog post: https://blog.chmouel.com/posts/gosmee-webhook-forwarder-relayer

Screenshot

Screenshot

Live Event Feed

The web interface of the gosmee server features a live event feed that shows webhook events in real-time:

  • Live status indicator showing connection state
  • Event counter showing number of received events
  • JSON tree viewer for easy payload inspection
  • Copy buttons for headers and payloads
  • Replay functionality to resend events to your endpoint
  • Clear button to remove all events from the feed

Each event in the feed shows:

  • Event ID and timestamp
  • Headers with copy functionality
  • Payload in both tree view and raw JSON formats
  • Option to replay individual events

Installation

Release

Please visit the release page and choose the appropriate archive or package for your platform.

Homebrew

brew tap chmouel/gosmee https://github.com/chmouel/gosmee
brew install gosmee
yay -S gosmee-bin

Docker

Gosmee client with Docker

docker run ghcr.io/chmouel/gosmee:latest

Gosmee server with Docker

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

go install -v github.com/chmouel/gosmee@latest

Git

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 Services

System service example files for macOS and Linux are available in the misc directory.

Kubernetes

You can deploy gosmee on Kubernetes to relay webhooks to your internal services.

Two deployment configurations are available:

Server Deployment

The server deployment exposes a public webhook endpoint to receive incoming webhook events:

kubectl apply -f misc/gosmee-server-deployment.yaml

Key configuration:

  • Set --public-url to your actual domain where the service will be exposed
  • Configure an Ingress with TLS or use a service mesh for production use
  • For security, consider using --webhook-signature and --allowed-ips options

Client Deployment

The 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:

  • Adjust the first argument to your gosmee server URL or smee.io channel
  • Change the second argument to your internal service URL (e.g., http://service.namespace:8080)
  • The --saveDir flag enables saving webhook payloads to /tmp/save for later inspection

For detailed configuration options, please refer to the documentation comments in each deployment file.

Shell completion

Shell completions are available for gosmee:

# BASH
source <(gosmee completion bash)

# ZSH
source <(gosmee completion zsh)

Usage

Client

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).

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).

Executing commands on webhook events

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

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 --exec flag 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-signature on the server side to verify webhook authenticity.

Replay scripts

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 output

Examples:

# 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.

Server

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, access it with a URL format like:

https://myserverurl/RANDOM_ID

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

Caddy

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.

Nginx

Running gosmee server behind nginx requires some configuration:

    location / {
        proxy_pass         http://127.0.0.1:3333;
        proxy_set_header Connection '';
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        chunked_transfer_encoding off;
        proxy_read_timeout 372h;
    }

Note: Long-running connections may occasionally cause errors with nginx. Contributions to debug this are most welcome.

Security

Webhook IP Restrictions

To enhance security, gosmee server supports IP address restrictions for webhook POST requests. This feature allows you to:

  • Restrict webhook POST requests to specific IP addresses or CIDR ranges
  • Support both IPv4 and IPv6 addresses
  • Trust X-Forwarded-For and X-Real-IP headers when behind a proxy
  • Log rejected IP addresses for monitoring
  • Only affects POST requests - UI access remains unrestricted

Example usage with Git hosting providers' IP ranges:

# GitHub webhook IP ranges
gosmee server --allowed-ips 192.30.252.0/22 --allowed-ips 185.199.108.0/22 --allowed-ips 140.82.112.0/20 --trust-proxy

# GitLab.com webhook IP ranges
gosmee server --allowed-ips 35.231.145.151 --allowed-ips 34.74.90.64 --allowed-ips 34.74.226.93 --trust-proxy

# Bitbucket Cloud webhook IP ranges
gosmee server --allowed-ips 34.199.54.113 --allowed-ips 34.232.119.183 --allowed-ips 34.236.25.177 --allowed-ips 35.171.175.212 --trust-proxy

# Multiple providers can be combined
gosmee server --allowed-ips 192.30.252.0/22 --allowed-ips 35.231.145.151 --allowed-ips 34.199.54.113 --trust-proxy

Official IP ranges documentation:

Environment variables:

  • GOSMEE_ALLOWED_IPS: Comma-separated list of allowed IPs/CIDR ranges
  • GOSMEE_TRUST_PROXY: Set to any value to trust proxy headers

The server logs will show:

  • Rejected POST requests with the client's IP address
  • Whether the IP was obtained from proxy headers (when --trust-proxy is enabled)
  • Standard request logging including status code 403 for rejected IPs

Note: If no IP restrictions are configured, all POST requests will be allowed.

Payload Size and Memory Management
Server-Side

To protect server resources, gosmee server enforces a maximum payload size for incoming webhooks. By default, this limit is 25MB, matching GitHub's standard. You can configure this limit using the --max-body-size flag (value is in bytes).

Example:

# Set a custom 10MB limit
gosmee server --max-body-size 10485760
Client-Side

The gosmee client also has a buffer for receiving messages from the server. If you're forwarding payloads larger than the default, you'll need to increase this buffer.

  • Flag: --sse-buffer-size
  • Default: 1048576 (1MB)
  • Description: Sets the maximum buffer size in bytes for the client's connection.

Example:

# Set a 5MB buffer for the client
gosmee client --sse-buffer-size 5242880 <SMEE_URL> <TARGET_URL>
Important Considerations

Increasing payload and buffer sizes has security and performance implications:

  • Increased Memory Consumption: Both the client and server will consume more memory when these limits are raised. A server handling many simultaneous large webhooks, or a client with a large buffer, can use quite a bit of RAM.
  • Denial-of-Service (DoS) Risk: Exposing a server with a very large max-body-size can make it a target for DoS attacks, where an attacker sends huge payloads to exhaust server memory.
  • Kubernetes Deployments: If you're running gosmee in Kubernetes and increase these limits, you must update the memory requests and limits in the gosmee-server-deployment.yaml and gosmee-client-deployment.yaml files. Failure to do so may cause your Pods to be OOMKilled (Out Of Memory) by Kubernetes.

Always set these values to the lowest practical limit that still accommodates your expected webhook sizes.

Channel Name Protection

To prevent potential DoS attacks and ensure system stability:

  • Channel names are limited to 64 characters maximum
  • All route handlers (/, /events/{channel}, /replay/{channel}, POST /{channel}) enforce this limit
  • Built-in validation for all endpoints that handle channel names
  • Protects against resource exhaustion attacks that could be caused by excessive channel name lengths
Webhook Signature Validation

When running gosmee server, you can enable webhook signature validation for multiple providers with multiple secrets:

gosmee server --webhook-signature=SECRET1 --webhook-signature=SECRET2

When enabled:

  • For GitHub: Validates X-Hub-Signature-256 header using HMAC SHA-256
  • For GitLab: Validates X-Gitlab-Token header using secure token comparison
  • For Bitbucket Cloud/Server: Validates X-Hub-Signature header using HMAC SHA-256
  • For Gitea/Forge: Validates X-Gitea-Signature header using HMAC SHA-256
  • Supports multiple secrets - useful when receiving webhooks from different sources
  • Rejects requests with missing or invalid signatures with HTTP 401 Unauthorised
  • Each secret is tried for validation, webhook is accepted if any secret matches
  • Performance impact is minimal: ~2μs per validation with negligible memory usage

You can also set multiple secrets via the GOSMEE_WEBHOOK_SIGNATURE environment variable by separating them with commas.

Command Execution (--exec)

The --exec flag (available on both client and replay commands) executes a shell command for each webhook event, with the JSON payload and headers available via temporary files ($GOSMEE_PAYLOAD_FILE and $GOSMEE_HEADERS_FILE). This is powerful but carries security risks:

  • Untrusted sources: If the gosmee server accepts webhooks from untrusted sources without --webhook-signature validation, an attacker can send crafted payloads. If your exec script passes unsanitized payload fields to shell commands (e.g., via $(jq -r .field)), this could lead to command injection.
  • Mitigations:
    • Use --webhook-signature on the server to verify webhook authenticity
    • Use --allowed-ips to restrict which IPs can send webhooks
    • In your exec scripts, avoid passing raw payload fields to shell evaluation
    • Use --exec-on-events to limit which event types trigger execution

Replay Webhook Deliveries via the GitHub API (beta)

If you'd rather not use a relay server with GitHub, you can replay webhook deliveries directly via the GitHub API.

This method is more reliable as you don't depend on relay server availability. You'll need a GitHub token with appropriate scopes:

  • For repository webhooks: read:repo_hook or repo scope
  • For organisation webhooks: admin:org_hook scope

Currently supports replaying webhooks from Repositories and Organisations (GitHub Apps webhooks not supported).

First, find the Hook ID:

gosmee replay --github-token=$GITHUB_TOKEN --list-hooks org/repo

List hooks for an organisation:

gosmee replay --github-token=$GITHUB_TOKEN --list-hooks org

Start listening and replaying events on a local server:

gosmee replay --github-token=$GITHUB_TOKEN org/repo HOOK_ID http://localhost:8080

This will listen to all new events and replay them to http://localhost:8080.

Replay all events received since a specific time (UTC format 2023-12-19T12:31:12):

gosmee replay --time-since=2023-12-19T09:00:00 --github-token=$GITHUB_TOKEN org/repo HOOK_ID http://localhost:8080

To find the right date, list all deliveries:

gosmee replay --github-token=$GITHUB_TOKEN --list-deliveries org/repo HOOK_ID

Note

gosmee replay doesn't support paging yet and lists only the last 100 deliveries. Specifying a date older than the last 100 deliveries won't work.

When rate limited, gosmee will fail without recovery mechanisms.

Replay Viewer Utility

Screen.Recording.2025-05-22.at.08.41.49.online-video-cutter.com.mp4

Gosmee includes a helper script misc/replayview for interactively browsing, previewing, and replaying webhook events saved by the client (--saveDir). This tool lets you:

  • Fuzzy-find replay shell scripts and their JSON payloads
  • Preview event metadata, headers, and payloads
  • Copy replay script paths to clipboard
  • Create symlinks for quick access
  • Run replay scripts directly
  • Interactively inspect JSON payloads (requires fx)

Usage:

./misc/replayview -h

By default, it looks for replay files in /tmp/save or /tmp/replay. Use -d <dir> to specify a different directory.

It will create a symbolic link of the chosen replay event to the file /tmp/run.sh, which redirects the event to the local service for easy payload replay.

Requirements: fzf, jq, fd, and optionally fx for interactive JSON viewing.

See the script header or run with -h for full options and details.

Beyond Webhook

Gosmee is webhook-specific. For other tunnelling solutions, check https://github.com/anderspitman/awesome-tunneling. Recommended alternatives include go-http-tunnel or tailscale.

Caveats

This tool is intended for local development and testing environments only. It hasn't undergone thorough security and performance reviews and should not be deployed in production systems.

smee-sidecar is a service intended for monitoring gosmee deployments. It provides active health checks to verify that gosmee is serving requests.

Thanks

  • Most of the work is powered by the go-sse library.
  • I previously used pysmee but its underlying SSE library had issues with chunked transfers, that leads me to rewrite it in Go and add some specific features needed for my use cases.

Copyright

Apache-2.0

Authors

Chmouel Boudjnah

About

Command line server and client for webhooks deliveries (and https://smee.io)

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors