voyvodka/webhook-engine
Self-hosted webhook delivery engine with retry, circuit breaker, HMAC signing, rate limiting, and real-time React dashboard. .NET 10 + PostgreSQL. Docker one-liner deploy.
README Snapshot
WebhookEngine
webhook.sametozkan.com.tr · Docker Hub · NuGet
Self-hosted webhook delivery platform with reliable at-least-once delivery, exponential backoff retries, per-endpoint circuit breakers, and a real-time dashboard.
Features
- Reliable delivery -- PostgreSQL-backed queue with
SELECT ... FOR UPDATE SKIP LOCKED, no Redis/RabbitMQ needed - At-least-once semantics -- messages are never lost; stale lock recovery handles worker crashes
- Exponential backoff -- 7 retry attempts (5s, 30s, 2m, 15m, 1h, 6h, 24h)
- Circuit breaker -- per-endpoint, auto-opens after 5 consecutive failures, 5-minute cooldown
- HMAC-SHA256 signing -- Standard Webhooks spec (
webhook-id,webhook-timestamp,webhook-signature) - Idempotency -- optional
idempotencyKeyprevents duplicate deliveries - Real-time dashboard -- React SPA with live delivery feed via SignalR
- Single process -- API + background workers + dashboard served from one ASP.NET Core host
- Data retention -- automatic cleanup (delivered: 30 days, dead-letter: 90 days)
Tech Stack
| Layer | Technology |
|---|---|
| Backend | C# / .NET 10, ASP.NET Core, Entity Framework Core |
| Database | PostgreSQL 17+ |
| Frontend | React 19, TypeScript 5.9, Vite 7, Tailwind CSS 4, Recharts 3, Lucide React |
| Real-time | SignalR |
| Testing | xUnit, FluentAssertions, NSubstitute, Testcontainers |
| Logging | Serilog (structured JSON) |
| Validation | FluentValidation |
| Observability | OpenTelemetry + Prometheus metrics exporter |
| Deployment | Docker Compose |
Quick Start
Docker Compose (recommended)
git clone https://github.com/voyvodka/webhook-engine.git
cd webhook-engine
docker compose -f docker/docker-compose.yml up -d
The app starts on http://localhost:5100. Dashboard login: [email protected] / changeme.
Local Development
Prerequisites: .NET 10 SDK, PostgreSQL 17+, Node.js 20+, Yarn
- Start PostgreSQL (or use the dev compose file):
docker compose -f docker/docker-compose.dev.yml up -d
- Configure connection string in
src/WebhookEngine.API/appsettings.json:
{
"ConnectionStrings": {
"Default": "Host=localhost;Port=5432;Database=webhookengine;Username=webhookengine;Password=webhookengine"
}
}
- Run the backend (migrations auto-apply on startup):
dotnet run --project src/WebhookEngine.API
The API starts on http://localhost:5128.
- Run the dashboard (optional, for frontend development):
cd src/dashboard
yarn install
yarn dev
Dashboard dev server runs on http://localhost:5173 with API proxy to localhost:5128.
Documentation
- Getting Started — from zero to first webhook
- Self-Hosting Guide — production deployment and operations
- Release Guide — Docker Hub and NuGet publishing flow
- Roadmap — current phase status and upcoming priorities
- PRD — product scope, goals, and requirement definitions
- API Reference — full endpoint documentation
- Architecture — system design and component overview
- Database — schema and PostgreSQL notes
- Contributing — local setup and pull request workflow
- Changelog — notable project changes
- Samples guide, Sample Sender, and Sample Receiver
- Signature verification helpers for C#, TypeScript, and Python
Architecture
+---------------------------+
| ASP.NET Core Host |
| |
HTTP requests -> | Controllers (REST API) |
| Middleware (auth, logging) |
| Static files (React SPA) |
| SignalR Hub |
| |
| Background Workers: |
| - DeliveryWorker |
| - RetryScheduler |
| - CircuitBreakerWorker |
| - StaleLockRecovery |
| - RetentionCleanup |
+------------+--------------+
|
v
+---------------------------+
| PostgreSQL 17+ |
| - Data storage |
| - Job queue (SKIP LOCKED) |
+---------------------------+
Solution Structure
src/
WebhookEngine.Core/ # Domain: entities, enums, interfaces, options
WebhookEngine.Infrastructure/ # EF Core, PostgreSQL queue, repositories, services
WebhookEngine.Application/ # DI registration (CQRS scaffold, not yet implemented)
WebhookEngine.Worker/ # Background services (delivery, retry, circuit breaker)
WebhookEngine.API/ # ASP.NET Core host, controllers, middleware
WebhookEngine.Sdk/ # .NET client SDK
dashboard/ # React SPA (Vite + TypeScript)
tests/
WebhookEngine.Core.Tests/
WebhookEngine.Infrastructure.Tests/
WebhookEngine.Application.Tests/
WebhookEngine.API.Tests/
WebhookEngine.Worker.Tests/
API Overview
Base URL: /api/v1/
Authentication
- API key (for programmatic access):
Authorization: Bearer whe_{appId}_{random} - Cookie auth (for dashboard):
POST /api/v1/auth/login
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/health or /api/v1/health |
None | Health check |
POST |
/api/v1/auth/login |
None | Dashboard login |
POST |
/api/v1/auth/logout |
Cookie | Dashboard logout |
GET |
/api/v1/auth/me |
Cookie | Current user |
GET |
/api/v1/applications |
Cookie | List applications |
POST |
/api/v1/applications |
Cookie | Create application |
GET |
/api/v1/applications/{id} |
Cookie | Get application |
PUT |
/api/v1/applications/{id} |
Cookie | Update application |
DELETE |
/api/v1/applications/{id} |
Cookie | Delete application |
POST |
/api/v1/applications/{id}/rotate-key |
Cookie | Rotate API key |
GET |
/api/v1/event-types |
API key | List event types |
POST |
/api/v1/event-types |
API key | Create event type |
GET |
/api/v1/endpoints |
API key | List endpoints |
POST |
/api/v1/endpoints |
API key | Create endpoint |
PUT |
/api/v1/endpoints/{id} |
API key | Update endpoint |
DELETE |
/api/v1/endpoints/{id} |
API key | Delete endpoint |
POST |
/api/v1/endpoints/{id}/disable |
API key | Disable endpoint |
POST |
/api/v1/endpoints/{id}/enable |
API key | Enable endpoint |
POST |
/api/v1/messages |
API key | Send message |
POST |
/api/v1/messages/batch |
API key | Batch send messages |
POST |
/api/v1/messages/replay |
API key | Replay historical messages |
GET |
/api/v1/messages |
API key | List messages |
GET |
/api/v1/messages/{id} |
API key | Get message |
GET |
/api/v1/messages/{id}/attempts |
API key | List attempts |
POST |
/api/v1/messages/{id}/retry |
API key | Retry message |
GET |
/api/v1/dashboard/overview |
Cookie | Dashboard stats |
GET |
/api/v1/dashboard/timeline |
Cookie | Delivery chart data |
GET |
/api/v1/dashboard/event-types |
Cookie | List event types (cross-app) |
POST |
/api/v1/dashboard/event-types |
Cookie | Create event type |
PUT |
/api/v1/dashboard/event-types/{id} |
Cookie | Update event type |
DELETE |
/api/v1/dashboard/event-types/{id} |
Cookie | Archive event type |
Send a Message
curl -X POST http://localhost:5128/api/v1/messages \
-H "Authorization: Bearer whe_abc123_your-api-key" \
-H "Content-Type: application/json" \
-d '{
"eventType": "order.created",
"payload": {"orderId": 42, "amount": 99.99},
"idempotencyKey": "order-42"
}'
Response:
{
"data": {
"messageIds": ["msg_abc123..."],
"endpointCount": 2,
"eventType": "order.created"
},
"meta": { "requestId": "req_..." }
}
.NET SDK
using WebhookEngine.Sdk;
using var client = new WebhookEngineClient("whe_abc_your-api-key", "http://localhost:5128");
await client.Messages.SendAsync(new SendMessageRequest
{
EventType = "order.created",
Payload = new { orderId = 42, amount = 99.99 },
IdempotencyKey = "order-42"
});
Webhook Signature Verification
WebhookEngine signs every delivery with HMAC-SHA256 following the Standard Webhooks spec. Receivers should verify signatures like this:
# Python example
import hmac, hashlib, base64
def verify_webhook(body: bytes, secret: str, headers: dict) -> bool:
msg_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
signature = headers["webhook-signature"]
payload = f"{msg_id}.{timestamp}.{body.decode()}"
secret_bytes = base64.b64decode(secret)
expected = hmac.new(secret_bytes, payload.encode(), hashlib.sha256).digest()
expected_sig = f"v1,{base64.b64encode(expected).decode()}"
return hmac.compare_digest(signature, expected_sig)
Configuration
All configuration is via appsettings.json or environment variables (double-underscore notation):
| Setting | Default | Description |
|---|---|---|
ConnectionStrings__Default |
-- | PostgreSQL connection string |
WebhookEngine__Delivery__TimeoutSeconds |
30 |
HTTP delivery timeout |
WebhookEngine__Delivery__BatchSize |
10 |
Messages dequeued per batch |
WebhookEngine__Delivery__PollIntervalMs |
1000 |
Queue poll interval (empty queue) |
WebhookEngine__Delivery__StaleLockMinutes |
5 |
Stale lock recovery threshold |
WebhookEngine__RetryPolicy__MaxRetries |
7 |
Max delivery attempts |
WebhookEngine__RetryPolicy__BackoffSchedule |
[5,30,120,900,3600,21600,86400] |
Backoff in seconds |
WebhookEngine__CircuitBreaker__FailureThreshold |
5 |
Failures to open circuit |
WebhookEngine__CircuitBreaker__CooldownMinutes |
5 |
Cooldown before half-open |
WebhookEngine__DashboardAuth__AdminEmail |
[email protected] |
Initial admin email |
WebhookEngine__DashboardAuth__AdminPassword |
changeme |
Initial admin password |
WebhookEngine__Retention__DeliveredRetentionDays |
30 |
Days to keep delivered messages |
WebhookEngine__Retention__DeadLetterRetentionDays |
90 |
Days to keep dead-letter messages |
Build & Test
# Build
dotnet build WebhookEngine.sln
# Run all tests (106 tests)
dotnet test WebhookEngine.sln
# Run specific test project
dotnet test tests/WebhookEngine.Core.Tests
# Run tests matching a pattern
dotnet test --filter "DisplayName~HmacSigning"
# Dashboard build
cd src/dashboard && yarn install && yarn build
Docker
# Production
docker compose -f docker/docker-compose.yml up -d
# Stop production services
docker compose -f docker/docker-compose.yml down
# Reset production data (removes PostgreSQL volume)
docker compose -f docker/docker-compose.yml down -v
# Development (starts PostgreSQL only, run backend separately)
docker compose -f docker/docker-compose.dev.yml up -d
dotnet run --project src/WebhookEngine.API
docker/docker-compose.yml uses a persistent PostgreSQL volume (pgdata), so old applications/endpoints remain after restart unless you run down -v.
Prometheus Metrics
WebhookEngine exposes Prometheus metrics at GET /metrics. No authentication required.
Changelog
Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project follows Semantic Versioning.
[Unreleased]
Added
- No unreleased changes yet.
[0.1.3] - 2026-04-08
Added
- Landing page at webhook.sametozkan.com.tr — project overview, features, quick start, and links to GitHub, Docker Hub, NuGet, and docs.
Changed
README.md: added website, Docker Hub, and NuGet links to the header.WebhookEngine.Sdk: version aligned with main project (0.1.3);PackageProjectUrlupdated to the landing page.
[0.1.0] - 2026-03-02
Added
- Sample applications for end-to-end webhook flow:
samples/WebhookEngine.Sample.Sender(SDK-based sender)samples/WebhookEngine.Sample.Receiver(signature-verifying receiver)
- Signature verification helpers in C#, TypeScript, and Python under
samples/signature-verification/. - New guides:
docs/GETTING-STARTED.mddocs/SELF-HOSTING.md
- Contribution and collaboration files:
CONTRIBUTING.md- issue templates and PR template under
.github/
- Release workflow
.github/workflows/release.ymlfor Docker Hub and NuGet publishing on version tags. samples/README.mdwith end-to-end sample run instructions.- Message API enhancements:
POST /api/v1/messages/batchfor batch event enqueue.POST /api/v1/messages/replayfor replaying messages by date range and filters.
- Endpoint rate limiting support (
rateLimitPerMinutein endpoint metadata) with worker integration. - Dashboard event type management capabilities:
- Backend CRUD endpoints under
/api/v1/dashboard/event-types. - New dashboard page and navigation for event type management.
- Backend CRUD endpoints under
- Expanded filtering in dashboard message log (application, endpoint, date range).
- Additional automated coverage:
- API integration tests for dashboard event type flow, dashboard message filters, and API key active/inactive behavior.
- Repository tests for message and endpoint filtering/count/replay selection behavior.
- Infrastructure/worker tests for endpoint health transitions, rate limiter behavior, and circuit breaker worker transitions.
Changed
README.mdupdated with documentation and samples links.docs/ROADMAP.mdstatuses updated for completed Phase 1 tasks (1.1-1.6).docs/MVP-ROADMAP.mdupdated to mark sample app completion.- API response envelope standardization applied across dashboard/auth/application surfaces.
- Endpoint health now drives endpoint status transitions (
active,degraded,failed) while preservingdisabledendpoints. - CI frontend workflow now includes explicit
yarn lintstep and usesyarn typecheck.
Fixed
- Release workflow compatibility for tag releases by removing invalid job-level
ifconditions that referencedsecrets.
Releases
- v0.1.3 — Landing Page & SDK Alignment
## WebhookEngine v0.1.3 Documentation and packaging update accompanying the new project landing page. ### Changes - **landing page:** new project site at [webhook.sametozkan.com.tr](https://webhook.sametozkan.com.tr) — features, quick start, and links to all resources - **readme:** website, Docker Hub, and NuGet links added to the header - **sdk:** version aligned with main project (`0.1.3`); package project URL updated to the landing page - **docs:** removed stale internal planning files (backlog-v0.1.1, triage-flow, typescript-sdk-demand-criteria) ### Links - [Docker Hub](https://hub.docker.com/r/voyvodka/webhook-engine) — `docker pull voyvodka/webhook-engine:0.1.3` - [NuGet](https://www.nuget.org/packages/WebhookEngine.Sdk/0.1.3) — `dotnet add package WebhookEngine.Sdk --version 0.1.3` - [Documentation](https://webhook.sametozkan.com.tr)
Open on GitHub - v0.1.2 — Stabilization Patch
## WebhookEngine v0.1.2 Bug fixes, dependency updates, and dashboard reliability improvements. ### Fixes - **circuit-breaker limbo:** Messages for circuit-open endpoints no longer get stuck in Pending forever — they now increment attemptCount and eventually dead-letter as expected - **FluentValidation migration:** Replaced deprecated `FluentValidation.AspNetCore` with custom action filter using `FluentValidation.DependencyInjectionExtensions 12.1.1` - **dashboard analytics:** Fixed SQL alias casing in timeline query for EF Core mapping - **SignalR reconnection:** Client now retries indefinitely with exponential backoff (capped at 30s) instead of giving up after 4 attempts - **delivery-log route:** Removed broken parameterless `/delivery-log` nav item that errored without a messageId ### Chores - **NuGet:** EF Core 10.0.5, removed unused Serilog/Http packages from Infrastructure and Worker - **Frontend:** Vite 8, TypeScript 6, lucide-react 1.7, and other dependency upgrades - **Login page:** Removed debug credentials, improved UX with monospace font and error animation ### Links - Docker Hub: [voyvodka/webhook-engine](https://hub.docker.com/r/voyvodka/webhook-engine) - NuGet: [WebhookEngine.Sdk](https://www.nuget.org/packages/WebhookEngine.Sdk)
Open on GitHub - v0.1.1 — Stabilization Patch
## WebhookEngine v0.1.1 Post-stabilization patch with documentation sync and a SQL mapping fix. ### Fixes - **fix:** PascalCase SQL aliases in `DashboardStatsRepository` for correct EF Core `SqlQueryRaw` property mapping ### Documentation - Updated `AGENTS.md` and `ARCHITECTURE.md` to reflect Phase 3 DashboardController split (4 controllers) and CQRS scaffold removal - Fixed dev port references in `CONTRIBUTING.md` (5100 → 5128 for `dotnet run`) ### Stabilization Summary (v0.1.0 → v0.1.1) This release concludes the v0.1.0 stabilization milestone: - 5 phases, 13 plans, 18/18 requirements satisfied - Circuit breaker race condition fixed, message status state machine added - Per-AppId rate limiting, configurable idempotency window - DashboardController split into 4 single-responsibility controllers - Dashboard overview query consolidated (9 queries → 1) - 3 ADRs written (replay scope, CQRS removal, payload transformation) - All 5 GitHub issues closed **Full Changelog:** https://github.com/voyvodka/webhook-engine/compare/v0.1.0...v0.1.1
Open on GitHub - v0.1.0 — Initial Release
## WebhookEngine v0.1.0 First public release of WebhookEngine — a self-hosted webhook delivery platform. ### Core Features - **Queue-based delivery** — PostgreSQL SKIP LOCKED queue, no Redis/RabbitMQ needed - **Retry with backoff** — 7 attempts with exponential backoff (5s → 24h) - **Circuit breaker** — per-endpoint health tracking with automatic open/close via advisory locks - **HMAC signing** — Svix-compatible Standard Webhooks signatures - **Rate limiting** — per-application token bucket on public endpoints - **Configurable idempotency** — per-app deduplication window (default 24h) - **Real-time dashboard** — React 19 + SignalR live delivery monitoring - **Prometheus metrics** — OpenTelemetry instrumentation built-in - **Single container** — API + workers + dashboard in one Docker image ### Quick Start ```bash docker pull voyvodka/webhook-engine:0.1.0 docker compose -f docker/docker-compose.yml up ``` Dashboard: `http://localhost:5100` — Login: `[email protected]` / `changeme` ### Links - 📦 [Docker Hub](https://hub.docker.com/r/voyvodka/webhook-engine) - 📦 [NuGet SDK](https://www.nuget.org/packages/WebhookEngine.Sdk) - 📖 [API Documentation](https://github.com/voyvodka/webhook-engine/blob/main/docs/API.md) - 🚀 [Self-Hosting Guide](https://github.com/voyvodka/webhook-engine/blob/main/docs/SELF-HOSTING.md) - 🏗️ [Architecture](https://github.com/voyvodka/webhook-engine/blob/main/docs/ARCHITECTURE.md)
Open on GitHub