{"id":49478108,"url":"https://github.com/pomelo-studios/pomelohook","last_synced_at":"2026-05-01T22:00:42.713Z","repository":{"id":354662016,"uuid":"1221204174","full_name":"Pomelo-Studios/PomeloHook","owner":"Pomelo-Studios","description":"Webhook forwarding tunnel with CLI, dashboard, and org-scoped tunnels","archived":false,"fork":false,"pushed_at":"2026-04-30T19:17:16.000Z","size":693,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T21:20:24.995Z","etag":null,"topics":["cli","developer-tools","go","self-hosted","tunnel","webhook","webhook-forwarder"],"latest_commit_sha":null,"homepage":"https://hook.pomelostudios.net/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Pomelo-Studios.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-25T22:18:45.000Z","updated_at":"2026-04-30T19:17:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Pomelo-Studios/PomeloHook","commit_stats":null,"previous_names":["pomelo-studios/pomelohook"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/Pomelo-Studios/PomeloHook","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pomelo-Studios%2FPomeloHook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pomelo-Studios%2FPomeloHook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pomelo-Studios%2FPomeloHook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pomelo-Studios%2FPomeloHook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Pomelo-Studios","download_url":"https://codeload.github.com/Pomelo-Studios/PomeloHook/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pomelo-Studios%2FPomeloHook/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32514340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","developer-tools","go","self-hosted","tunnel","webhook","webhook-forwarder"],"created_at":"2026-04-30T21:00:20.754Z","updated_at":"2026-05-01T22:00:42.672Z","avatar_url":"https://github.com/Pomelo-Studios.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PomeloHook\n\n![CI](https://github.com/Pomelo-Studios/PomeloHook/actions/workflows/ci.yml/badge.svg)\n\nSelf-hosted webhook relay and inspection tool. Exposes a public URL, forwards incoming webhooks to a local machine through a WebSocket tunnel, stores every event in SQLite, and provides a web dashboard for inspection and replay.\n\nThink ngrok — but self-hosted, team-aware, and built around persistent event history. No accounts, no rate limits, no data leaving your infrastructure.\n\n---\n\n## How It Works\n\n```\nExternal service\n      │\n      │  POST https://your-server/webhook/{subdomain}\n      ▼\n┌─────────────────┐       WebSocket tunnel      ┌─────────────────┐\n│     Server      │   ──────────────────────►   │   CLI client    │\n│  (Go + SQLite)  │   ◄──────────────────────   │  (your machine) │\n└────────┬────────┘                             └────────┬────────┘\n         │                                               │\n         │  stores event                                 │  forwards to\n         ▼                                               ▼\n    pomelodata.db                                 localhost:{port}\n```\n\n1. The CLI opens a persistent WebSocket connection to the server.\n2. When a webhook arrives at `/webhook/{subdomain}`, the server saves it to SQLite **first**, then forwards it through the WebSocket to the CLI.\n3. The CLI proxies the request to your local service and reports the result back.\n4. The dashboard at `localhost:4040` shows all events in real time, with full request/response detail and replay.\n\nEvents are always stored regardless of whether forwarding succeeds — they are always replayable.\n\n---\n\n## Features\n\n- **Personal tunnels** — each user gets their own subdomain, private to them\n- **Org tunnels** — shared across the org, multiple subscribers receive the same webhook simultaneously\n- **30-day retention** — events auto-deleted after 30 days (configurable)\n- **Replay** — resend any stored event to any URL from the CLI or dashboard\n- **Local dashboard** — embedded in the CLI binary, no separate install\n- **Admin panel** — web UI for managing users, orgs, tunnels, and the SQLite database; served at `/admin` on the server\n- **No CGO** — pure-Go SQLite, single binary deployment\n\n---\n\n## Prerequisites\n\n| Tool | Version |\n|------|---------|\n| Go   | 1.22+   |\n| Node | 22+     |\n| npm  | 9+      |\n\n---\n\n## Server Setup\n\n### 1. Build and run\n\n```bash\nmake build\n./bin/pomelo-hook-server\n```\n\nOr run directly:\n\n```bash\ncd server \u0026\u0026 go run main.go\n```\n\nThe server listens on port `8080` by default.\n\n### 2. Environment variables\n\n| Variable               | Default          | Description                          |\n|------------------------|------------------|--------------------------------------|\n| `PORT`                 | `8080`           | HTTP listen port                     |\n| `POMELO_DB_PATH`       | `pomelodata.db`  | Path to the SQLite database file     |\n| `POMELO_RETENTION_DAYS`| `30`             | Days before events are auto-deleted  |\n\n### 3. Initialize your first organization and admin user\n\nRun the interactive init command on first setup:\n\n```bash\n./bin/pomelo-hook-server init\n```\n\nIt will prompt for:\n- Organization name\n- Admin name and email\n- Admin password (min 8 characters, input hidden)\n\nOn success it prints your API key — save it. You can then log in with the CLI:\n\n```bash\npomelo-hook login --server https://your-server.com --email you@example.com\n```\n\nAfter this, use the admin panel at `https://your-server.com/admin` to manage additional users and set their passwords via the **Users → Set Password** action.\n\n---\n\n## CLI Usage\n\n### Install\n\nOne-line install (Linux and macOS, `amd64`/`arm64`):\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Pomelo-Studios/PomeloHook/main/install.sh | sh\n```\n\nOr build from source:\n\n```bash\nmake build\n# binary at ./bin/pomelo-hook\n```\n\nOr run directly:\n\n```bash\ncd cli \u0026\u0026 go run main.go \u003ccommand\u003e\n```\n\n### Commands\n\n#### `login` — authenticate with a server\n\n```bash\npomelo-hook login --server https://your-server.com --email alice@acme.com\n```\n\nFetches your API key from the server and saves it to `~/.pomelo-hook/config.json`.\n\n---\n\n#### `connect` — open a tunnel\n\n```bash\npomelo-hook connect --port 3000\n```\n\n- Opens a WebSocket tunnel to the server\n- Forwards incoming webhooks to `localhost:3000`\n- Starts the dashboard at `http://localhost:4040`\n- Prints the public webhook URL\n\n```\nTunnel: https://your-server.com/webhook/a1b2c3d4 → localhost:3000\nDashboard: http://localhost:4040\nPress Ctrl+C to stop\n```\n\n**Org tunnel:**\n\n```bash\npomelo-hook connect --org --tunnel my-team-tunnel --port 3000\n```\n\nMultiple team members or CI processes can subscribe to the same org tunnel at once — each receives its own copy of every incoming webhook.\n\n---\n\n#### `list` — show recent events\n\n```bash\npomelo-hook list\npomelo-hook list --last 50\npomelo-hook list --last 20 --tunnel \u003ctunnel-id\u003e\n```\n\nPrints a summary line per event:\n\n```\n[a1b2c3d4] ✓ POST /webhooks/stripe → 200 (14:32:01)\n[b5e6f7a8] ✗ POST /webhooks/github → 0  (14:31:55)\n```\n\n---\n\n#### `replay` — resend an event\n\n```bash\npomelo-hook replay \u003cevent-id\u003e\npomelo-hook replay \u003cevent-id\u003e --to http://localhost:4000\n```\n\nDefault target is `http://localhost:3000`. The server re-sends the original request body and method to the target URL.\n\n---\n\n## Dashboard\n\nThe dashboard is automatically served at `http://localhost:4040` while `connect` is running.\n\n- **Event list** — live-updating stream of all received webhooks\n- **Event detail** — full request headers, body, response status and body, latency\n- **Replay** — send any event to a target URL and see the result inline\n\nThe dashboard is embedded in the CLI binary and requires no separate install.\n\n---\n\n## Org Dashboard\n\nThe org dashboard is served at `https://your-server.com/app` and is embedded in the server binary.\n\n- **Tunnel list** — sidebar showing all org tunnels with live active/inactive status and the hostname of the connected device\n- **Event detail** — full request/response detail for the selected tunnel's events\n- Requires authentication; any org member can access it\n\n---\n\n## Admin Panel\n\nThe admin panel is served at `https://your-server.com/admin` and is embedded in the server binary. Only users with `role='admin'` can access it.\n\n### Accessing the panel\n\n1. Navigate to `https://your-server.com/admin`\n2. Enter your email and password — set via `pomelo-hook-server init` or the **Users → Set Password** admin action\n3. The session is stored in `sessionStorage` (cleared on tab close)\n\nWhen accessing the panel through the CLI dashboard (`http://localhost:4040/admin`), authentication is handled automatically via the CLI's API key.\n\n### What you can manage\n\n| Section | Capabilities |\n|---------|-------------|\n| **Users** | List, create, edit, delete org users; rotate API keys |\n| **Organizations** | View and rename your organization |\n| **Tunnels** | List all org tunnels; delete or force-disconnect active connections |\n| **Database** | Browse any SQLite table with pagination; run raw SQL queries (write queries require confirmation) |\n\n---\n\n## API Reference\n\nAll endpoints except `POST /api/auth/login` require `Authorization: Bearer \u003capi_key\u003e`.\n\n| Method | Path                          | Description                        |\n|--------|-------------------------------|------------------------------------|\n| GET    | `/api/health`                 | Health check (no auth)             |\n| POST   | `/api/auth/login`             | Return API key for an email        |\n| GET    | `/api/me`                     | Return the authenticated user      |\n| POST   | `/api/tunnels`                | Create a personal or org tunnel    |\n| GET    | `/api/tunnels`                | List tunnels visible to the caller |\n| GET    | `/api/ws?tunnel_id=\u003cid\u003e`      | Upgrade to WebSocket tunnel        |\n| GET    | `/api/events?tunnel_id=\u003cid\u003e`  | List events (default limit: 50)    |\n| POST   | `/api/events/{id}/replay`     | Replay event to a target URL       |\n| GET    | `/api/orgs/users`             | List org users                     |\n| GET    | `/api/org/tunnels`            | List all org tunnels with live status and active device |\n\nAdmin endpoints (require `role='admin'`):\n\n| Method | Path                                  | Description                            |\n|--------|---------------------------------------|----------------------------------------|\n| GET    | `/api/admin/users`                    | List all org users                     |\n| POST   | `/api/admin/users`                    | Create a user                          |\n| PUT    | `/api/admin/users/{id}`               | Update user (email, name, role)        |\n| DELETE | `/api/admin/users/{id}`               | Delete a user                          |\n| POST   | `/api/admin/users/{id}/rotate-key`    | Rotate a user's API key                |\n| POST   | `/api/admin/users/{id}/set-password`  | Set a user's password                  |\n| GET    | `/api/admin/orgs`                     | Get the organization                   |\n| PUT    | `/api/admin/orgs`                     | Update org name                        |\n| GET    | `/api/admin/tunnels`                  | List all org tunnels                   |\n| DELETE | `/api/admin/tunnels/{id}`             | Delete a tunnel and its events         |\n| POST   | `/api/admin/tunnels/{id}/disconnect`  | Force-disconnect an active tunnel      |\n| GET    | `/api/admin/db/tables`                | List SQLite tables                     |\n| GET    | `/api/admin/db/tables/{name}`         | Browse table rows (`?limit=\u0026offset=`)  |\n| POST   | `/api/admin/db/query`                 | Run a raw SQL query                    |\n\nWebhook ingestion (no auth):\n\n| Method | Path                     | Description                            |\n|--------|--------------------------|----------------------------------------|\n| ANY    | `/webhook/{subdomain}`   | Receive webhook for a tunnel subdomain |\n\n---\n\n## Development\n\n### Run all tests\n\n```bash\nmake test\n```\n\nOr per module:\n\n```bash\ncd server \u0026\u0026 go test ./...\ncd cli \u0026\u0026 go test ./...\ncd dashboard \u0026\u0026 npm test\n```\n\n### Build dashboard separately\n\n```bash\ncd dashboard \u0026\u0026 npm run dev     # dev server at localhost:5173\ncd dashboard \u0026\u0026 npm run build   # production build → cli/dashboard/static/\n```\n\n**Note:** Run `make dashboard` (or `npm run build`) before building the CLI binary. The CLI embeds the static files at compile time via `go:embed`. A fresh `go build` without the static directory will fail.\n\n### Project structure\n\n```\nserver/      Go relay server (API, WebSocket, SQLite)\ncli/         Go CLI client (tunnel, forwarder, embedded dashboard)\ndashboard/   React + Vite web UI\ndocs/        Architecture, deployment, and API reference\nbin/         Compiled binaries (gitignored)\n```\n\n---\n\n## Deployment notes\n\n- The server is a single stateless binary + one SQLite file. No external database required.\n- Run behind a reverse proxy (nginx, Caddy) with TLS — the CLI and server communicate over WebSocket, which requires standard HTTP upgrade support.\n- For WebSocket support with nginx, ensure `proxy_http_version 1.1` and the `Upgrade`/`Connection` headers are forwarded.\n\nExample Caddy config:\n\n```\nyour-server.com {\n    reverse_proxy localhost:8080\n}\n```\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpomelo-studios%2Fpomelohook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpomelo-studios%2Fpomelohook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpomelo-studios%2Fpomelohook/lists"}