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.

0stars
0forks
0watchers
0open issues
status: CORElanguage: C#branch: mainlicense: MIT Licenseupdated: 4/9/2026, 6:57:30 AMlast push: 4/9/2026, 6:57:26 AM
aspnet-corecircuit-breakerdashboarddockerdotnethmacpostgresqlreactreal-timeretryself-hostedsignalrtypescriptwebhookwebhook-deliverywebhooks

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 idempotencyKey prevents 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

  1. Start PostgreSQL (or use the dev compose file):
docker compose -f docker/docker-compose.dev.yml up -d
  1. Configure connection string in src/WebhookEngine.API/appsettings.json:
{
  "ConnectionStrings": {
    "Default": "Host=localhost;Port=5432;Database=webhookengine;Username=webhookengine;Password=webhookengine"
  }
}
  1. Run the backend (migrations auto-apply on startup):
dotnet run --project src/WebhookEngine.API

The API starts on http://localhost:5128.

  1. 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

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); PackageProjectUrl updated 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.md
    • docs/SELF-HOSTING.md
  • Contribution and collaboration files:
    • CONTRIBUTING.md
    • issue templates and PR template under .github/
  • Release workflow .github/workflows/release.yml for Docker Hub and NuGet publishing on version tags.
  • samples/README.md with end-to-end sample run instructions.
  • Message API enhancements:
    • POST /api/v1/messages/batch for batch event enqueue.
    • POST /api/v1/messages/replay for replaying messages by date range and filters.
  • Endpoint rate limiting support (rateLimitPerMinute in 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.
  • 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.md updated with documentation and samples links.
  • docs/ROADMAP.md statuses updated for completed Phase 1 tasks (1.1-1.6).
  • docs/MVP-ROADMAP.md updated 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 preserving disabled endpoints.
  • CI frontend workflow now includes explicit yarn lint step and uses yarn typecheck.

Fixed

  • Release workflow compatibility for tag releases by removing invalid job-level if conditions that referenced secrets.

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