| internal | ||
| pkg/config | ||
| static | ||
| templates | ||
| .gitignore | ||
| .golangci.yml | ||
| config.yaml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
| TODO.md | ||
webhooker
webhooker is, at its core, a webhook proxy. it receives webhooks and stores them in a database, then delivers them (either fire-and-forget, or with retries until successful) to a configured endpoint.
webhook endpoints are inputs to an event processor. there can be multiple webhook endpoint urls configured as inputs to any given processor. each processor can have 0 or more targets. all events delivered to the event processor will be delivered to all targets. such an object is called a 'delivery'. (this is not related to whether or not the target was successful in delivering the event, just that it was proxied.)
each target type can deliver the webhook in a different way. the first target type simply POSTs the webhook to a configured URL, doing a direct fire-and-forget proxy. the duration, result, status code, and any other metadata is logged in the database associated with the 'delivery' object (which is the pairing of that event with that target).
the second target type is a 'retry' target. this target will retry the delivery of the event to the target until it is successful, or until a per-target maximum retry count is reached. the retry target will exponentially back off the retry attempts, starting at 1 second and increasing 10x each time.
the third target type is a database target. this target will store the event in the database, but will not attempt to deliver it to any endpoint.
the fourth target type is a log target. this target will log the event to the console, but will not attempt to deliver it to any endpoint.
features
- store and forward (retries can be set to unlimited)
- multiple webhook endpoints per processor
- metrics availability in prometheus format for observability
- web interface for managing processors, webhooks, and targets
- web interface for viewing deliveries, events, and delivery results
- web interface for manually redelivering events to targets
database tables
users- these are the users of the webhooker service. each user has a unique username and password, and can have multiple API keys.processors- many to one with users.- each processor has a retention period, which is the number of days that events will be retained in the database before being deleted.
webhooks- these are the URLs that feed into processors. many to one.targets- these are the delivery targets for each processor. many to one with processors.- each target has a type, which can be one of the following:
http- this target will deliver the event to a configured URL via HTTP POST.retry- this target will retry the delivery of the event until it is successful or a maximum retry count is reached.- each 'retry' target can have a maximum queue size, which is the maximum number of events that can be queued for retry delivery. when the queue size is reached, the oldest events will be purged from the queue.
database- this target will store the event in a database, but will not attempt to deliver it to any endpoint.log- this target will log the event to the console, but will not attempt to deliver it to any endpoint.
- each target has a type, which can be one of the following:
deliveries- these are the delivery attempts for each target. many to one with targets.events- these are the events that are delivered to targets. one to 0 or more with deliveries, based on targetsdelivery_results- these are the results of each delivery attempt. many to one with deliveries, but 1-to-1 in the usual case.
Web Application
webhooker is a go application that provides a standard non-SPA web interface for managing one's processors, webhooks, targets, and API keys. it allows you to view your deliveries, events, and delivery results, and to optionally manually redeliver events to targets.
the web application is implemented primarily without javascript, using bootstrap for styling and layout. it uses go templates for rendering HTML, and those templates are embedded in the binary using go:embed.
architecture
webhooker uses Uber's fx dependency injection library for managing application lifecycle. it uses log/slog for structured logging. it uses gorm for database access, and initially uses sqlite for the database.
Development Policies and Practices
Code Structure and Organization
Package Layout
- All application code lives under
internal/to prevent external imports - Main entry point is in
cmd/webhooker/ - Package structure:
internal/config- Configuration management using Viperinternal/database- Database connections and migrationsinternal/globals- Global application metadata (version, build info)internal/handlers- HTTP handlers using closure patterninternal/healthcheck- Health check endpoint logicinternal/logger- Structured logging setupinternal/middleware- HTTP middleware (auth, CORS, logging, metrics)internal/server- HTTP server setup and routingstatic/- Static assets (CSS, JS)templates/- Go HTML templates
Dependency Injection
- Use Uber's fx for dependency injection and lifecycle management
- All components should be provided via fx.Provide in main.go
- Use parameter structs with fx.In for dependency injection
- Follow fx lifecycle hooks (OnStart, OnStop) for initialization
HTTP Routing and Handlers
- Use chi router for HTTP routing
- Handlers are methods on a Handlers struct that return http.HandlerFunc
- Use closure pattern for handlers:
func (h *Handlers) HandleIndex() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // handler logic here } } - Group related routes using chi's Route method
- Apply middleware in correct order: recovery, request ID, logging, metrics, CORS, timeout
Configuration
- Use Viper for configuration management
- Support environment variables with automatic binding
- Use
.envfiles for local development (via godotenv/autoload) - Set sensible defaults for all configuration values
- Configuration precedence: env vars > config file > defaults
Logging
- Use zerolog for structured JSON logging
- Always log in UTC
- Use console writer for TTY, JSON for production
- Include request ID in all request-scoped logs
- Log levels: Debug (development only), Info, Warn, Error, Fatal
- Include relevant context in all log messages
Error Handling
- Use Sentry for error reporting in production (if SENTRY_DSN is set)
- Implement panic recovery middleware
- Return appropriate HTTP status codes
- Log all errors with context
Database
- Use GORM for database operations
- Support SQLite for development, PostgreSQL for production
- Run migrations on startup
- Use UTC for all timestamps
- Implement proper connection pooling
Testing and Quality
- Write tests for all new functionality
- Use table-driven tests where appropriate
- Mock external dependencies in tests
- Achieve reasonable test coverage (not 100% required)
- Use
testifyfor assertions
Development Workflow
- Use Makefile with standard targets:
make test- Run all tests withgo test -v ./...make fmt- Format code withgo fmtmake lint- Run golangci-lintmake build- Build the binary- Default target is
test
- Pre-commit checks:
make test && make fmt && make lint - Use golangci-lint for linting with appropriate configuration
Code Style
- Follow standard Go idioms and effective Go guidelines
- Use constants for repeated values (no magic numbers/strings)
- Define constants at package level or top of file
- Keep functions small and focused
- Use meaningful variable names
- Comment exported functions and types
- Use structured types for request/response near usage
Security
- Implement proper authentication for admin endpoints
- Use basic auth for metrics endpoint
- Sanitize all user input
- Use prepared statements for database queries
- Don't log sensitive information (passwords, API keys)
Observability
- Expose Prometheus metrics on
/metrics(behind auth) - Include standard HTTP metrics (request count, duration, size)
- Add custom metrics for business logic
- Implement health check endpoint at
/.well-known/healthcheck.json - Include version info in health check response
Static Assets
- Embed static files and templates in binary using go:embed
- Serve static files from
/s/path - Use Bootstrap for UI without heavy JavaScript
- Prefer server-side rendering over SPA
Docker
- Multi-stage Dockerfile for minimal image size
- Run as non-root user in container
- Include health check in Dockerfile
- Use Alpine Linux for smaller images
Current Implementation Status
What's Working
-
Core Infrastructure
- Dependency injection using Uber's fx
- Structured logging with Go's standard log/slog (replaced zerolog)
- Configuration management with Viper
- Environment variable support with godotenv
- Pure Go SQLite database driver (modernc.org/sqlite) - no CGO required
-
Web Server
- HTTP server with chi router
- Middleware stack (recovery, request ID, logging, CORS, metrics, timeout)
- Static file serving from embedded filesystem
- JSON API endpoints
- Health check endpoint at
/.well-known/healthcheck.json - Metrics endpoint at
/metrics(with basic auth)
-
Database Models
- Full GORM models for all entities (users, processors, webhooks, targets, deliveries, events, delivery_results)
- Argon2id password hashing with secure defaults
- Auto-creation of admin user on first startup (password logged once)
-
Testing
- Unit tests for globals, logger, and database packages
- Database connection test that verifies SQLite works without CGO
- All tests pass
-
Development Tools
- Makefile with test, fmt, lint, build targets
- golangci-lint configuration (some deprecated linters need updating)
- Docker multi-stage build (updated to support CGO if needed)
- gitignore file
API Endpoints
GET /- Returns JSON welcome messageGET /.well-known/healthcheck.json- Health check with uptime, version infoGET /s/*- Static file serving (CSS, JS, Bootstrap)GET /metrics- Prometheus metrics (requires auth)GET /api/v1/*- API route group (not implemented yet)
Next Steps
-
Authentication
- Implement user login/logout
- API key generation and validation
- Session management
- Protected routes
-
Core Features
- Webhook receiver endpoints
- Event processing logic
- Target delivery system (HTTP, retry, database, log)
- Web UI templates for management
-
API Implementation
- CRUD endpoints for processors, webhooks, targets
- Event viewing and filtering
- Delivery status and retry management
-
Cleanup
- Update golangci-lint config to remove deprecated linters
- Add more comprehensive tests
- Implement proper error handling
Running the Application
# Development
make dev
# Build and run
make build
./bin/webhooker
# Run tests
make test
# Docker
make docker-build
make docker-run
Environment Variables
DEBUG- Enable debug loggingDEVELOPMENT_MODE- Enable development featuresPORT- Server port (default: 8080)DBURL- Database connection stringMETRICS_USERNAME- Username for metrics endpointMETRICS_PASSWORD- Password for metrics endpointSENTRY_DSN- Sentry error reporting (optional)
purpose
- prometheus/grafana metric analysis of webhook frequency, size, and handler performance
- introspection of webhook data
- debugging of webhook delivery issues
- redelivery of webhook events for application testing and development
- storage of webhook events for later analysis
- webhook delivery to multiple targets
- webhook delivery with retries for not-always-on applications
- can serve as an HA endpoint for ingestion to deliver events to less reliable systems
future plans
- email source type
- email delivery target
- sns delivery target
- s3 delivery target
- data transformations (e.g. to convert event notification data to slack messages)
- slack delivery target
- slack source type
- text file or jsonl delivery target, with periodic uploading to s3