|
| 1 | +``` |
| 2 | + _ _ _ _ |
| 3 | +| |__ ___ ___ | | _| |__ (_)_ __ |
| 4 | +| '_ \ / _ \ / _ \| |/ / '_ \| | '_ \ |
| 5 | +| | | | (_) | (_) | <| |_) | | | | | |
| 6 | +|_| |_|\___/ \___/|_|\_\_.__/|_|_| |_| |
| 7 | +``` |
| 8 | + |
| 9 | +**Single-binary webhook inbox. Accept HTTP, store it, show it. No external dependencies.** |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +- **One binary, zero dependencies** — no Postgres, no Redis, no Docker Compose. `scp` it to a box, run it, done. |
| 14 | +- **Embedded SQLite** — WAL mode, crash-safe, deterministic resource bounds. Data lives in a single directory. |
| 15 | +- **Live dashboard** — built-in SPA with live polling, syntax-highlighted JSON, request inspector with full headers and body. |
| 16 | +- **134 tests** — every handler, every edge case, every error path. `cargo test` is the only gate. |
| 17 | +- **Tiny footprint** — optimized release build with LTO, stripped symbols, and `panic = abort`. |
| 18 | + |
| 19 | +## Quick Start |
| 20 | + |
| 21 | +```bash |
| 22 | +# 1. Build (or download a release binary) |
| 23 | +cargo build --release |
| 24 | + |
| 25 | +# 2. Run |
| 26 | +./target/release/hookbin serve --port 8080 |
| 27 | + |
| 28 | +# 3. Create a hook and send a webhook |
| 29 | +curl -s http://localhost:8080/api/hooks -X POST | jq . |
| 30 | +# => { "id": "abc123xyz", "url": "/h/abc123xyz", ... } |
| 31 | + |
| 32 | +curl -X POST http://localhost:8080/h/abc123xyz \ |
| 33 | + -H "Content-Type: application/json" \ |
| 34 | + -d '{"event": "deploy", "status": "success"}' |
| 35 | +# => 200 OK |
| 36 | + |
| 37 | +# 4. Open http://localhost:8080 to see it in the dashboard |
| 38 | +``` |
| 39 | + |
| 40 | +## Dashboard |
| 41 | + |
| 42 | +The embedded dashboard provides a complete webhook inspection UI — no separate frontend service needed. |
| 43 | + |
| 44 | +- **Hook list** with request counts and creation timestamps |
| 45 | +- **Request timeline** with method badges and relative timestamps |
| 46 | +- **Request inspector** — headers, body, query params, and metadata |
| 47 | +- **Syntax-highlighted JSON** with collapsible body viewer |
| 48 | +- **Live polling** — new requests appear automatically |
| 49 | +- **Responsive layout** — works on desktop and mobile |
| 50 | + |
| 51 | +## API Endpoints |
| 52 | + |
| 53 | +| Method | Path | Description | |
| 54 | +|--------|------|-------------| |
| 55 | +| `POST` | `/h/{hook_id}` | Ingest a webhook (the money endpoint) | |
| 56 | +| `GET` | `/api/hooks` | List all hooks | |
| 57 | +| `POST` | `/api/hooks` | Create a new hook | |
| 58 | +| `GET` | `/api/hooks/{id}` | Get hook details | |
| 59 | +| `DELETE` | `/api/hooks/{id}` | Delete a hook | |
| 60 | +| `GET` | `/api/hooks/{id}/requests` | List captured requests | |
| 61 | +| `GET` | `/api/hooks/{id}/requests/{rid}` | Get a single request | |
| 62 | +| `GET` | `/health` | Health check | |
| 63 | +| `GET` | `/` | Dashboard (embedded SPA) | |
| 64 | + |
| 65 | +All API endpoints return structured JSON with error suggestions: |
| 66 | + |
| 67 | +```json |
| 68 | +{ |
| 69 | + "error": "not found: hook abc123xyz", |
| 70 | + "suggestion": "Check the hook ID and try again" |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +## Configuration |
| 75 | + |
| 76 | +Three-layer config: **defaults** -> **TOML file** -> **CLI flags** (CLI always wins). |
| 77 | + |
| 78 | +### CLI Flags |
| 79 | + |
| 80 | +```bash |
| 81 | +hookbin serve [OPTIONS] |
| 82 | +``` |
| 83 | + |
| 84 | +| Flag | Default | Description | |
| 85 | +|------|---------|-------------| |
| 86 | +| `--port` | `3000` | Port to listen on | |
| 87 | +| `--data` | `./hookbin-data` | Data directory for SQLite database | |
| 88 | +| `--max-hooks` | `100` | Maximum number of hooks | |
| 89 | +| `--max-payload` | `1048576` | Maximum payload size in bytes (1 MB) | |
| 90 | +| `--retention` | `86400` | Request retention in seconds (24 hours) | |
| 91 | +| `--rate-limit` | `60` | Rate limit per hook (requests/minute) | |
| 92 | +| `--max-requests` | `1000` | Max stored requests per hook | |
| 93 | +| `--config` | — | Path to TOML config file | |
| 94 | + |
| 95 | +### TOML Config |
| 96 | + |
| 97 | +```toml |
| 98 | +# hookbin.toml |
| 99 | +port = 8080 |
| 100 | +data = "/var/lib/hookbin" |
| 101 | +max_hooks = 50 |
| 102 | +max_payload = 2097152 |
| 103 | +retention = 172800 |
| 104 | +rate_limit = 120 |
| 105 | +max_requests = 500 |
| 106 | +``` |
| 107 | + |
| 108 | +```bash |
| 109 | +hookbin serve --config hookbin.toml |
| 110 | +``` |
| 111 | + |
| 112 | +## Resource Limits |
| 113 | + |
| 114 | +Hookbin follows the [TigerBeetle philosophy](https://tigerbeetle.com/): deterministic resource usage, pre-allocated bounds, no surprise OOM. |
| 115 | + |
| 116 | +| Resource | Default | Configurable | |
| 117 | +|----------|---------|--------------| |
| 118 | +| Max hooks | 100 | `--max-hooks` | |
| 119 | +| Max payload size | 1 MB | `--max-payload` | |
| 120 | +| Request retention | 24 hours | `--retention` | |
| 121 | +| Rate limit per hook | 60 req/min | `--rate-limit` | |
| 122 | +| Max requests per hook | 1,000 | `--max-requests` | |
| 123 | +| SQLite WAL mode | Always on | — | |
| 124 | + |
| 125 | +Everything is bounded. Nothing grows without limit. |
| 126 | + |
| 127 | +## Build from Source |
| 128 | + |
| 129 | +```bash |
| 130 | +# Prerequisites: Rust stable toolchain |
| 131 | +git clone https://github.com/copyleftdev/hookbin.dev.git |
| 132 | +cd hookbin.dev |
| 133 | + |
| 134 | +# Development |
| 135 | +cargo build # Debug build |
| 136 | +cargo test # Run all tests |
| 137 | +cargo run -- serve # Run locally on :3000 |
| 138 | + |
| 139 | +# Release |
| 140 | +cargo build --release # Optimized binary |
| 141 | +ls -lh target/release/hookbin |
| 142 | +``` |
| 143 | + |
| 144 | +### Make Targets |
| 145 | + |
| 146 | +| Target | Description | |
| 147 | +|--------|-------------| |
| 148 | +| `make build` | Debug build | |
| 149 | +| `make release` | Optimized release build | |
| 150 | +| `make test` | Run all tests | |
| 151 | +| `make fmt` | Format code | |
| 152 | +| `make clippy` | Run linter | |
| 153 | +| `make check` | Full pre-commit (fmt + clippy + test + build) | |
| 154 | +| `make run` | Run server on :8080 | |
| 155 | +| `make dev` | Run with `./data` directory | |
| 156 | +| `make size` | Show release binary size | |
| 157 | +| `make clean` | Remove build artifacts | |
| 158 | + |
| 159 | +## Project Structure |
| 160 | + |
| 161 | +``` |
| 162 | +src/ |
| 163 | + main.rs # Entry point, CLI parsing |
| 164 | + server.rs # Axum router + server setup |
| 165 | + db.rs # SQLite connection, migrations, queries |
| 166 | + models.rs # Hook, Request structs |
| 167 | + config.rs # CLI args + TOML config (3-layer merge) |
| 168 | + error.rs # AppError with structured suggestions |
| 169 | + retention.rs # Background cleanup task |
| 170 | + rate_limit.rs # In-process token bucket |
| 171 | + handlers/ |
| 172 | + ingest.rs # POST /h/{hook_id} — capture webhook |
| 173 | + hooks.rs # CRUD hooks |
| 174 | + requests.rs # List/inspect requests |
| 175 | + dashboard.rs # Serve embedded UI |
| 176 | + health.rs # Health check |
| 177 | +ui/ |
| 178 | + index.html # Dashboard SPA |
| 179 | + assets/ |
| 180 | + app.js # Vanilla JS — hash routing, live polling |
| 181 | + style.css # Dashboard styles |
| 182 | +``` |
| 183 | + |
| 184 | +## Tech Stack |
| 185 | + |
| 186 | +| Component | Implementation | |
| 187 | +|-----------|---------------| |
| 188 | +| Language | Rust (stable) | |
| 189 | +| HTTP server | Axum 0.8 + Tokio | |
| 190 | +| Storage | SQLite via rusqlite (bundled, WAL mode) | |
| 191 | +| UI embedding | rust-embed (compiled into binary) | |
| 192 | +| CLI | clap 4 | |
| 193 | +| Serialization | serde + serde_json | |
| 194 | +| Error handling | thiserror | |
| 195 | +| Logging | tracing + tracing-subscriber | |
| 196 | + |
| 197 | +## License |
| 198 | + |
| 199 | +MIT |
0 commit comments