A minimal webhook inspection tool. Open it in a browser, get a unique URL, send HTTP requests to it, and watch them appear in real time.
Think RequestBin or Webhook.site — but self-hosted and simple.
- Open
http://localhost:8080— a room is created automatically - You get a capture URL:
http://localhost:8080/r/{roomID} - Send any HTTP request to that URL
- It appears instantly in the browser via WebSocket
- Open a second tab using the
?room=<roomID>URL — past requests replay, then live ones stream in - Rooms expire after 1 hour (TTL enforced in Redis)
- Go — HTTP server, WebSocket hub, Redis client
- chi — routing
- gorilla/websocket — WebSocket handling
- go-redis — Redis storage
- xid — unique room ID generation
- structured-logger — structured JSON logging
- Vanilla HTML/CSS/JS — no framework, no build step
cmd/server/main.go entry point
internal/room/room.go Request struct
internal/store/redis.go Redis persistence (Save, GetAll) + circuit breaker + retry
internal/store/circuit_breaker.go circuit breaker (closed → open → half-open)
internal/hub/hub.go in-memory pub/sub fan-out
internal/handler/http.go POST /rooms, ANY /r/{roomID}, debug endpoints
internal/handler/websocket.go GET /ws/{roomID}
internal/handler/trace.go trace ID middleware
internal/metrics/metrics.go atomic counters
static/index.html single-page frontend
Prerequisites: Go 1.22+, Redis
# install and start Redis (Ubuntu/Debian)
sudo apt install redis-server -y
sudo systemctl start redis
# run the server
go run ./cmd/serverOpen http://localhost:8080.
| Variable | Default | Description |
|---|---|---|
REDIS_ADDR |
localhost:6379 |
Redis address |
PORT |
8080 |
HTTP server port |
| Endpoint | Description |
|---|---|
POST /rooms |
Create a new room |
ANY /r/{roomID} |
Capture an incoming webhook |
GET /ws/{roomID} |
WebSocket — replay + live stream |
GET /healthz |
Health check (pings Redis) |
GET /metrics |
Plain-text counters |
GET /debug/room/{roomID} |
Live room state (subscribers, stored requests) |
GET /debug/room/{roomID}/request/{reqID} |
Full request detail by ID |
- Graceful shutdown — SIGINT/SIGTERM drains in-flight requests (10s timeout)
- HTTP timeouts — read 10s, write 30s, idle 60s
- Panic recovery — per-request, server keeps running
- Redis retry — 3 attempts with exponential backoff (50ms → 100ms → 200ms)
- Circuit breaker — trips after 5 consecutive Redis failures, resets after 30s cooldown
- Fallback queue — up to 500 requests buffered in memory when Redis is down, drained on recovery
- WebSocket limits — 512 byte read limit, 10s write deadline per message
Every request logs structured JSON with trace_id, room_id, method, path, latency_ms.
The X-Trace-Id header is returned on every response.
curl http://localhost:8080/healthz
# {"status":"ok","redis":"up"}
curl http://localhost:8080/metrics
# active_ws_connections 2
# total_webhooks_captured 47
# redis_errors_total 0
# redis_circuit_breaker closed
# fallback_queue_depth 0# run all tests with race detector
go test ./internal/... -race
# verbose
go test ./internal/... -v -race 2>/dev/null | grep -E "^(=== RUN|--- PASS|--- FAIL|FAIL|ok)"Covers: circuit breaker state machine, hub fan-out, HTTP handlers, WebSocket replay, 1000-concurrent load test, chaos (Redis down/recovery).
curl -X POST http://localhost:8080/r/{roomID} \
-H "Content-Type: application/json" \
-d '{"event": "test"}'The request appears in the browser immediately.