Skip to content

A self-hostable webhook relay for local development. Forward webhooks from external services to your localhost.

License

Notifications You must be signed in to change notification settings

lance0/hookshot

Repository files navigation

Hookshot

The self-hosted webhook relay. Receive Stripe, GitHub, and other webhooks on localhost—without paying for ngrok.

Stripe ──→ your-server.fly.io ──→ tunnel ──→ localhost:3000

Why Hookshot?

  • Free forever — No $8/mo subscription, no signup, no limits
  • Self-hosted — Your webhooks never touch third-party servers
  • Replay included — Re-send any webhook for debugging (ngrok charges extra)
  • Route by path — Send /api to port 3000, /webhooks to port 4000
  • Beautiful TUI — Live request stream with Catppuccin colors

Quick Start

1. Deploy the server (on any VPS, Fly.io, Railway, etc.)

hookshot server --port 8080 --public-url https://hooks.yoursite.com

2. Connect locally

hookshot client -s https://hooks.yoursite.com -t http://localhost:3000
✓ Connected!
  Public URL: https://hooks.yoursite.com/t/a1b2c3d4-...
  Forwarding: http://localhost:3000
────────────────────────────────────────────
[15:04:05] → POST /webhooks/stripe (d08ba939)
[15:04:05] ← 200 OK (12ms)

3. Point your webhook provider to your public URL. Done.

Installation

go install github.com/lance0/hookshot/cmd/hookshot@latest

Or grab a binary from Releases.

Interactive TUI

hookshot client -s https://hooks.yoursite.com --tui
┌────────────────────────────────────────────────────────────────────┐
│  hookshot                           tunnel: a1b2c3d4  ● connected  │
│  https://hooks.yoursite.com/t/a1b2c3d4-...                         │
├────────────────────────────────────────────────────────────────────┤
│  REQUESTS                                     [r]eplay  [/]filter  │
│  ────────────────────────────────────────────────────────────────  │
│  ▸ POST   /webhooks/stripe     200   12ms   just now     d08ba939  │
│    POST   /webhooks/github     200   45ms   2s ago       f4a21c87  │
│    POST   /webhooks/slack      500    8ms   5s ago       b7e93d12  │
├────────────────────────────────────────────────────────────────────┤
│  REQUEST DETAIL                                                    │
│  ────────────────────────────────────────────────────────────────  │
│  POST /webhooks/stripe                                             │
│  Content-Type: application/json                                    │
│  {"type":"payment_intent.succeeded","amount":2000}                 │
└────────────────────────────────────────────────────────────────────┘
  ↑/↓ navigate   r replay   / filter   q quit

Inspect headers, bodies, and response times. Replay any request with r.

Features

Feature Description
Webhook Relay Forward external webhooks to localhost over WebSocket
Request Replay Re-send any webhook with one keypress
Path Routing Route different paths to different local ports
TUI Inspector Live request stream with search/filter
Auth Tokens Secure your relay with bearer tokens
TLS/HTTPS Native TLS support for production
Config Files YAML config for both server and client
Graceful Shutdown Clean disconnect on Ctrl+C
SQLite Storage Persistent request history across restarts

Configuration

Create hookshot.yaml:

server:
  port: 8080
  public_url: https://hooks.yoursite.com
  token: your-secret-token        # Recommended: require auth
  database: /var/lib/hookshot/requests.db  # Optional: persist requests
  # max_body_size: 10485760       # Optional: 10MB default
  # max_message_size: 10485760    # Optional: 10MB default
  # allowed_origins: []           # Optional: restrict WebSocket origins
  # tls_cert: /path/to/cert.pem   # Optional: enable HTTPS
  # tls_key: /path/to/key.pem

client:
  server: https://hooks.yoursite.com
  token: your-secret-token
  target: http://localhost:3000

  # Or route by path:
  # routes:
  #   - path: /api
  #     target: http://localhost:3000
  #   - path: /webhooks
  #     target: http://localhost:4000

CLI Reference

# Server
hookshot server [flags]
  -p, --port int            Port (default 8080)
      --public-url          Public URL for display
      --token               Auth token (recommended for production)
  -d, --database            SQLite database path (default: in-memory)
      --max-body-size       Max webhook body size (default 10MB)
      --max-message-size    Max WebSocket message size (default 10MB)
      --allowed-origins     Allowed WebSocket origins (default: all)
      --tls-cert            TLS certificate path
      --tls-key             TLS key path

# Client
hookshot client [flags]
  -s, --server            Server URL (required)
  -t, --target            Local target (default localhost:3000)
      --token             Auth token
      --tui               Interactive mode
  -v, --verbose           Show request/response bodies

# Utilities
hookshot requests --server URL --tunnel ID    # List recent requests
hookshot replay --server URL --tunnel ID --request ID   # Replay a request

API

Endpoint Description
POST /t/{tunnel}/* Receive webhooks
GET /api/tunnels/{id}/requests List stored requests
POST /api/tunnels/{id}/requests/{req}/replay Replay a request
GET /health Health check

Security

Hookshot is hardened for internet exposure:

  • UUID tunnel IDs — 36-character IDs prevent guessing
  • Request ownership — Replay API validates tunnel ownership
  • Size limits — Configurable body/message limits (default 10MB)
  • Origin validation — WebSocket origin checks
  • No query tokens — Auth only via headers (no URL leakage)

For production, always set --token and use TLS.

Deploy

Fly.io (recommended)

One command to deploy your own relay:

# Clone and deploy
git clone https://github.com/lance0/hookshot.git
cd hookshot
fly launch --copy-config --name my-hookshot

# Create persistent volume for request history
fly volumes create hookshot_data --size 1

# Set your auth token
fly secrets set TOKEN=your-secret-token

# Deploy and get your URL
fly deploy
fly status

Your relay is now live at https://my-hookshot.fly.dev. Connect with:

hookshot client -s https://my-hookshot.fly.dev --token your-secret-token

Docker

# With persistent storage
docker run -p 8080:8080 -v hookshot-data:/data hookshot server --token=secret

# Or in-memory only
docker run -p 8080:8080 -e DATABASE_PATH= hookshot server --token=secret

Build from source

git clone https://github.com/lance0/hookshot.git
cd hookshot
go build -o hookshot ./cmd/hookshot
./hookshot server --port 8080

vs ngrok

ngrok Hookshot
Price $8-20/mo Free
Self-hosted No Yes
Replay Paid add-on Included
Path routing No Yes
Your data Their servers Your servers

Hookshot is purpose-built for webhook development. For general HTTP tunneling or TCP proxying, ngrok may be a better fit.

License

MIT — use it however you want.


Built with Go. Themed with Catppuccin.

About

A self-hostable webhook relay for local development. Forward webhooks from external services to your localhost.

Resources

License

Stars

Watchers

Forks

Packages

No packages published