Skip to content

Isidorsson/polyhook-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

polyhook-go

Webhook bin with live SSE streaming and replay-with-retries — Go implementation.

This is one half of a two-implementation polyglot study. The Node twin is at Isidorsson/polyhook-node. Both implement the same openapi.yaml and are compared side-by-side at andreasisidorsson.com/projects/polyhook.

What it does

  • Create an ephemeral bin → get an ingest_url, a view_url, and a delete_token.
  • POST anything to ingest_url (any HTTP method, any content-type, any body).
  • Subscribe to the bin's SSE stream → captured requests arrive in real time.
  • Replay a captured request to a target URL with exponential-backoff retries.
  • Bins auto-expire after 24h.

Stack

Concern Choice
HTTP net/http + chi router
Persistence modernc.org/sqlite (pure-Go, no CGO)
Logging log/slog (structured JSON)
Metrics prometheus/client_golang (text exposition at /metrics)
Concurrency one goroutine per SSE subscriber, fan-out via sync.RWMutex
Rate limiting per-IP token bucket (10 req/s, burst 30) on ingest
Replay worker bounded chan queue, 4 worker goroutines, exp. backoff

Running locally

go mod download
go run .
# listens on :8080 by default; DB at ./polyhook.db

Try it:

# 1. Create a bin
curl -s -X POST http://localhost:8080/v1/bins | tee /tmp/bin.json
# {"id":"k3p9q2x7m1nb","ingest_url":"...","view_url":"...","delete_token":"..."}

INGEST=$(jq -r .ingest_url /tmp/bin.json)
VIEW=$(jq -r .view_url   /tmp/bin.json)
ID=$(jq -r .id           /tmp/bin.json)

# 2. POST anything to it
curl -s -X POST -H "Content-Type: application/json" \
  -d '{"event":"order.created","amount":4200}' "$INGEST"
# {"request_id":"...","received_at":"..."}

# 3. List captured requests (cursor-paginated, ETag-cacheable)
curl -s "$VIEW?limit=10" | jq

# 4. Stream new requests as they arrive (Ctrl-C to stop)
curl -N "http://localhost:8080/v1/bins/$ID/stream"

# 5. Replay any captured request
RID=$(curl -s "$VIEW" | jq -r '.items[0].id')
curl -s -X POST -H "Content-Type: application/json" \
  -d '{"target_url":"https://httpbin.org/post"}' \
  "http://localhost:8080/v1/bins/$ID/requests/$RID/replay"
# {"replay_id":"..."}

Configuration (env)

Var Default Notes
PORT 8080 HTTP listen port
DB_PATH /data/polyhook.db* SQLite file path (*falls back to ./polyhook.db if /data not writable)
PUBLIC_URL (derived from request) Sets the host returned in ingest_url/view_url
LOG_LEVEL info debug for verbose

Deploying to Railway

  1. Push this repo to GitHub.
  2. New Railway project → "Deploy from GitHub repo" → select polyhook-go.
  3. Railway auto-detects the Dockerfile. Set PUBLIC_URL to the public Railway URL once assigned.
  4. Add a volume mounted at /data for SQLite persistence.
  5. Healthcheck path: /healthz.

The image is ~12 MB (distroless static base) and the binary itself is ~10 MB. At idle the process holds ~15 MB RSS.

What's interesting in the code

File What to look at
store.go:5-50 Schema + WAL + SetMaxOpenConns(1) for a single writer
store.go:121 Cursor pagination via composite (received_at, id) key
main.go:51 SSE broadcaster: per-bin subscriber map, non-blocking publish (slow client must not stall ingest)
main.go:103 Token-bucket rate limiter (per-IP, lazily allocated)
main.go:158 Replay worker: 4 goroutines, exponential backoff 1s → 16s
main.go:280 Strong ETag over the response bytes; If-None-Match shortcut

Spec

See openapi.yaml. Both implementations conform to it.

License

MIT

About

Webhook bin with live SSE streaming and replay-with-retries — Go implementation. Twin of polyhook-node; both conform to one OpenAPI 3.1 spec.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors