A production-grade, scalable webhook delivery system built with Go, PostgreSQL, and Redis
- Overview
- Features
- Architecture
- Tech Stack
- Getting Started
- Database Schema
- Project Structure
- Development Phases
- Configuration
- Testing
- Roadmap
- Contributing
The Webhook Delivery Platform is a robust, system designed to reliably deliver webhook events to customer endpoints. Inspired by production systems like Svix, Stripe, and Convoy, this platform handles event ingestion, persistent storage, asynchronous delivery, retry logic with exponential backoff, and comprehensive observability.
- π Reliability First: Events are never lost, even during system failures
- β‘ High Performance: Asynchronous processing with worker pools
- π Smart Retries: Exponential backoff with jitter for transient failures
- π Full Observability: Complete audit trail of every delivery attempt
- π‘οΈ Security: HMAC-SHA256 signing for webhook authenticity
- π― Production Ready: Built with real-world patterns and best practices
- Event Ingestion API - Accept webhook events via
POST /events - Payload Validation - JSON schema validation with Gin bindings
- PostgreSQL Persistence - Events stored as JSONB for flexibility
- UUID Generation - Unique identifiers for all events
- Delivery Tracking - Separate table for delivery attempts and status
- Database Migrations - Version-controlled schema management
- Docker Compose Setup - One-command local development environment
- Health Checks - PostgreSQL readiness probes
- Error Handling - Comprehensive error responses
- Redis Queue Integration - Durable message queue with AOF persistence
- Worker Pool - 3 concurrent workers for parallel delivery
- Asynchronous Processing - Decoupled ingestion from delivery
- Crash Recovery - Automatic recovery of stalled jobs on restart
- Reliable Queue Pattern - BRPopLPush for atomic job processing
- Job Acknowledgment - Nack/Ack pattern for retry handling
- Retry Logic - Exponential backoff (5s β 5m β 30m β 2h β 12h)
- HMAC Signing - Cryptographic signatures for webhook security
- Dead Letter Queue - Handle permanently failed deliveries
- Metrics & Monitoring - Prometheus metrics and Grafana dashboards
- Rate Limiting - Protect customer endpoints from overload
- Admin Dashboard - Web UI for event inspection and replay
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client Application β
β (Sends webhook events) β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β API Service (Gin) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β POST /events β β
β β β’ Validate JSON payload β β
β β β’ Generate UUID β β
β β β’ Store in PostgreSQL β β
β β β’ Create delivery record β β
β β β’ Enqueue to Redis β β
β β β’ Return 202 Accepted β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PostgreSQL Database β
β ββββββββββββββββββββ ββββββββββββββββββββββββββββ β
β β events β β deliveries β β
β β β’ id (UUID) ββββββββ β’ id (UUID) β β
β β β’ event_type β β β’ event_id (FK) β β
β β β’ payload β β β’ endpoint_url β β
β β β’ created_at β β β’ status β β
β ββββββββββββββββββββ β β’ attempts β β
β β β’ next_retry_at β β
β β β’ last_error β β
β ββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Redis Queue (AOF Persistence Enabled) β
β β’ BRPopLPush for reliable delivery β
β β’ Crash recovery on startup β
β β’ 3 concurrent workers β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Worker Pool (3 Goroutines) β
β β’ Concurrent HTTP POST to customer endpoints β
β β’ Ack on success / Nack on failure β
β β’ Update delivery status in PostgreSQL β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Component | Technology | Purpose |
|---|---|---|
| Language | Go 1.25.6 | High-performance, concurrent backend |
| Web Framework | Gin | Fast HTTP router and middleware |
| Database | PostgreSQL 15 | Persistent event and delivery storage |
| Queue | Redis 7 (AOF) | Durable message queue with persistence |
| Driver | pgx/v5 | Native PostgreSQL driver with connection pooling |
| Containerization | Docker & Docker Compose | Local development environment |
| JSON Handling | encoding/json | Event payload serialization |
| UUID | uuid | Unique event identifiers |
| Queue Pattern | BRPopLPush | Reliable queue with crash recovery |
- Go 1.25.6 or higher
- Docker & Docker Compose
- PostgreSQL 15 (via Docker)
- Make (optional, for convenience commands)
-
Clone the repository
git clone https://github.com/Flack74/Webhook-Delivery-Platform.git cd Webhook-Delivery-Platform -
Set up environment variables
cp .env.example .env
Edit
.env:DB_NAME=webhook_delivery_platform DB_USER=webhookuser DB_PASSWORD=webhook123 DB_PORT=5432 DB_HOST=localhost
-
Start PostgreSQL with Docker
docker compose --env-file .env up -d
-
Wait for database to be ready
sleep 5
-
Run database migrations
PGPASSWORD=webhook123 psql -h localhost -U webhookuser -d webhook_delivery_platform -f migrations/001_init.sql
-
Install Go dependencies
go mod download
-
Run the application
DB_NAME=webhook_delivery_platform \ DB_USER=webhookuser \ DB_PASSWORD=webhook123 \ DB_PORT=5432 \ DB_HOST=localhost \ go run cmd/api/main.go
-
Verify the server is running
curl http://localhost:8000/health
Endpoint: POST /events
Description: Accept a webhook event, validate the payload, and store it in the database.
Request Headers:
Content-Type: application/json
Request Body:
{
"event_type": "user.created",
"data": {
"id": "usr_123",
"email": "user@example.com"
}
}Response: 202 Accepted
{
"status": "accepted",
"event_id": "550e8400-e29b-41d4-a716-446655440000"
}Error Response: 400 Bad Request
{
"error": "Key: 'Event.EventType' Error:Field validation for 'EventType' failed on the 'required' tag"
}Example with cURL:
curl -X POST http://localhost:8000/events \
-H "Content-Type: application/json" \
-d '{
"event_type": "user.created",
"data": {
"id": "usr_123",
"email": "user@example.com"
}
}'Stores all incoming webhook events immutably.
CREATE TABLE events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_events_event_type ON events (event_type);| Column | Type | Description |
|---|---|---|
id |
UUID | Unique event identifier |
event_type |
TEXT | Event category (e.g., "user.created") |
payload |
JSONB | Flexible JSON payload |
created_at |
TIMESTAMPTZ | Event creation timestamp |
Tracks delivery attempts for each event to customer endpoints.
CREATE TABLE deliveries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_id UUID NOT NULL REFERENCES events(id) ON DELETE CASCADE,
endpoint_url TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('pending', 'in_progress', 'succeeded', 'failed')),
attempts INTEGER NOT NULL DEFAULT 0,
next_retry_at TIMESTAMPTZ,
last_error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (event_id, endpoint_url)
);
CREATE INDEX idx_deliveries_pending ON deliveries (status, next_retry_at);| Column | Type | Description |
|---|---|---|
id |
UUID | Unique delivery identifier |
event_id |
UUID | Foreign key to events table |
endpoint_url |
TEXT | Customer webhook endpoint |
status |
TEXT | Delivery status (pending/in_progress/succeeded/failed) |
attempts |
INTEGER | Number of delivery attempts |
next_retry_at |
TIMESTAMPTZ | Scheduled retry time |
last_error |
TEXT | Last error message |
created_at |
TIMESTAMPTZ | Delivery record creation time |
updated_at |
TIMESTAMPTZ | Last update time |
Webhook-Delivery-Platform/
βββ cmd/
β βββ api/
β β βββ main.go # API server entry point
β βββ receiver/
β βββ main.go # Test webhook receiver (Phase 1)
βββ internal/
β βββ handler/
β β βββ events.go # Event creation handler
β β βββ routes.go # Route definitions
β βββ repository/
β β βββ events.go # Event database operations
β β βββ deliveries.go # Delivery database operations
β β βββ postgres.go # PostgreSQL connection setup
β βββ queue/
β β βββ queue.go # Redis queue (Phase 4)
β βββ worker/
β βββ worker.go # Delivery worker pool (Phase 4)
βββ migrations/
β βββ 001_init.sql # Initial schema
β βββ 002_drop.sql # Rollback script
βββ .env # Environment variables
βββ .gitignore
βββ docker-compose.yml # PostgreSQL container
βββ go.mod # Go dependencies
βββ go.sum
βββ Architecture.md # Detailed architecture diagram
βββ README.md # This file
This project follows an incremental build approach, implementing one concept at a time.
- HTTP server with Gin
POST /eventsendpoint- Payload validation
202 Acceptedresponse
- Test webhook receiver
- Direct HTTP POST to endpoint
- Success/failure logging
- In-memory queue with Go channels
- Worker goroutine
- Decoupled ingestion from delivery
- PostgreSQL integration
- Event and delivery tables
- Database migrations
- Connection pooling with pgx
- Redis integration with AOF persistence
- Durable message queue (BRPopLPush pattern)
- Worker pool (3 concurrent workers)
- Job acknowledgment (Ack/Nack)
- Crash recovery on startup
- Enhanced error logging
- Exponential backoff algorithm
- Retry scheduling
- Max attempt limits
- Transient vs permanent failure detection
- Delivery attempt logging
- Dead letter queue
- Metrics (Prometheus)
- Grafana dashboards
- HMAC-SHA256 signing
- Timestamp validation
- Replay attack prevention
- Rate limiting
- Manual replay API
- Admin dashboard
- Docker deployment
...
| Variable | Description | Default |
|---|---|---|
DB_NAME |
PostgreSQL database name | webhook_delivery_platform |
DB_USER |
PostgreSQL username | webhook8me |
DB_PASSWORD |
PostgreSQL password | webhooks74621 |
DB_PORT |
PostgreSQL port | 5432 |
DB_HOST |
PostgreSQL host | localhost |
GIN_MODE |
Gin mode (debug/release) | debug |
The application constructs the connection string as:
postgres://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?sslmode=disable
-
Start the server
go run cmd/api/main.go
-
Send a test event
curl -X POST http://localhost:8000/events \ -H "Content-Type: application/json" \ -d '{ "event_type": "order.completed", "data": { "id": "order_789", "email": "customer@example.com" } }'
-
Verify in database
PGPASSWORD=webhooks74621 psql -h localhost -U webhook8me -d webhook_delivery_platform
SELECT * FROM events ORDER BY created_at DESC LIMIT 5; SELECT * FROM deliveries ORDER BY created_at DESC LIMIT 5;
go test ./...- Phase 0: Event ingestion API
- Phase 1: Synchronous webhook delivery
- Phase 2: Asynchronous processing
- Phase 3: PostgreSQL persistence
- Phase 4: Redis queue integration
- Phase 5: Retry logic with exponential backoff
- Phase 6: Delivery attempt logging & DLQ
- Phase 7: HMAC-SHA256 signing
- Phase 8: Admin dashboard & metrics
- Phase 9: Multi-endpoint support
- Phase 10: Rate limiting & circuit breakers
- Phase 11: Kubernetes deployment
- Phase 12: Horizontal scaling & load balancing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by Svix, Convoy, and Hookdeck
- Built with guidance from webhooks.fyi
- PostgreSQL best practices from pgx documentation
- Gin framework by gin-gonic
Flack74 - @Flack74
Project Link: https://github.com/Flack74/Webhook-Delivery-Platform
β Star this repo if you find it helpful!
Made with β€οΈ By Flack74