RelayForge is a reliable webhook delivery service built with Go and PostgreSQL. It accepts events, creates one delivery for each active endpoint, and processes deliveries asynchronously with retry and recovery mechanisms.
The project focuses on backend reliability concepts including transactional persistence, concurrent job claiming, idempotency, retry scheduling, failure recovery, and graceful shutdown.
- Idempotent event submission
- Transactional event and delivery creation
- Multi-endpoint webhook fan-out
- Concurrent background delivery workers
- Atomic job claiming with PostgreSQL
- Exponential backoff with jitter
Retry-Afterhandling- Bounded delivery attempts
- Dead-letter states for exhausted deliveries
- Stale-claim recovery
- Graceful HTTP-server and worker shutdown
- Structured logging
- Unit, integration, concurrency, and end-to-end tests
flowchart TD
Client["Event Producer"] --> API["RelayForge HTTP API"]
API --> DB[("PostgreSQL")]
Worker["Delivery Workers"] --> DB
Worker --> Endpoint["Webhook Endpoints"]
Recovery["Recovery Runner"] --> DB
- A client submits an event with an idempotency key.
- RelayForge creates the event and its endpoint deliveries in one database transaction.
- Background workers atomically claim due deliveries.
- A worker sends the payload to the destination endpoint.
- RelayForge records the attempt and updates the delivery state.
- Retryable failures are scheduled using exponential backoff with jitter.
- Deliveries that exhaust their allowed attempts enter a terminal dead state.
- The recovery runner returns abandoned claims to the retry queue.
RelayForge provides at-least-once delivery.
A destination can receive the same webhook more than once if it successfully processes a request but RelayForge stops before recording the successful result. Receivers should therefore process webhooks idempotently and deduplicate requests using the stable delivery identifier.
RelayForge does not guarantee exactly-once delivery.
A delivery moves through states such as:
pending
→ processing
→ succeeded
processing
→ retry_scheduled
→ processing
processing
→ dead
If a worker stops after claiming a delivery, stale-claim recovery makes the delivery eligible for processing again after its claim lease expires.
The event and all corresponding delivery records are inserted in one PostgreSQL transaction. A partial fan-out is never committed.
Workers claim due deliveries through a database operation that prevents competing workers from successfully claiming the same available row simultaneously.
Temporary failures are retried using exponential backoff with jitter. When supported by the destination response, Retry-After influences the next eligible attempt time.
A process can stop after committing a claim but before finishing the delivery. RelayForge periodically identifies expired claims and returns them to the retry queue.
During normal shutdown, RelayForge stops accepting new HTTP requests, allows active handlers to finish, cancels background processing, and waits for workers and recovery tasks to exit.
- Go
- PostgreSQL
Set the database url in cmd/relayforge/main.go
The database must exist and have the repository migrations applied before starting the service.
Verify the database connection:
go run ./cmd/relayforge pingStart RelayForge:
go run ./cmd/relayforge serveThe server listens on:
http://localhost:8080
set the test database url in internal/delivery/delivery_integration_test.go and internal/store/postgres/store_integration_test.go
Run the complete test suite:
go test -p=1 ./... -count=1Run it with Go's race detector:
go test -race -p=1 ./... -count=1The tests cover behavior including:
- Transaction rollback
- Duplicate event submissions
- Concurrent worker claiming
- Retry scheduling
- Temporary and permanent delivery failures
- Request cancellation
- Stale-claim recovery
- Graceful worker termination
- End-to-end webhook delivery
Integration-test packages are currently serialized with -p=1 because they share one resettable PostgreSQL test database.
cmd/relayforge/ Command-line entry point
internal/delivery/ Sending, processing, and retry policy
internal/httpapi/ HTTP handlers and API behavior
internal/store/postgres/ PostgreSQL persistence
internal/worker/ Delivery workers and stale-claim recovery
migrations/ Database migrations
- Complete docs and .env usage are not implemented yet.
- Webhook request signing is not implemented yet.
- Containerized local deployment is not available yet.
- Automated CI checks are not configured yet.
- Production metrics and distributed tracing are not implemented yet.
- Integration-test packages share one test database.
- At-least-once delivery permits duplicate requests after ambiguous failures.
- RelayForge has not yet been validated for production workloads.
- Add HMAC webhook signing
- Add Docker and Docker Compose
- Add CI/CD
RelayForge is an actively developed educational project. Its current purpose is to demonstrate and deepen my understanding of reliable backend-system design; it should not yet be treated as a production-hosted webhook service.