LowCodeHub.Webhooks
0.0.1
There is a newer version of this package available.
See the version list below for details.
See the version list below for details.
dotnet add package LowCodeHub.Webhooks --version 0.0.1
NuGet\Install-Package LowCodeHub.Webhooks -Version 0.0.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LowCodeHub.Webhooks" Version="0.0.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LowCodeHub.Webhooks" Version="0.0.1" />
<PackageReference Include="LowCodeHub.Webhooks" />
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LowCodeHub.Webhooks --version 0.0.1
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: LowCodeHub.Webhooks, 0.0.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LowCodeHub.Webhooks@0.0.1
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LowCodeHub.Webhooks&version=0.0.1
#tool nuget:?package=LowCodeHub.Webhooks&version=0.0.1
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
LowCodeHub.Webhooks
A dynamic webhook invocation library for ASP.NET Core with database-driven subscriptions, outbox-based reliable delivery, resilience policies (via Microsoft.Extensions.Http.Resilience / Polly v8), and optional multi-tenant support.
Features
- Database-driven subscriptions — Webhook URLs, HTTP methods, headers, timeouts, and event types are stored in a configurable database table
- Outbox-based delivery — Delivery attempts are persisted before sending, ensuring at-least-once delivery even across crashes and restarts
- Two-level resilience — Immediate transient-fault retries (Polly) on each HTTP call + durable exponential-backoff retries via the outbox worker
- Multi-tenant support — Optional
TenantIdon subscriptions and deliveries for multi-tenant/multi-client scenarios - Typed events — Each subscription is tied to a specific event type (e.g.
"order.created","payment.completed") - Configurable HTTP method — POST, PUT, PATCH, or any HTTP method per subscription
- Custom headers — JSON-serialized headers per subscription (e.g. API keys, auth tokens)
- Snapshot-at-dispatch — Subscription config (URL, headers, method) is snapshotted into the delivery attempt at dispatch time, so later subscription changes don't affect in-flight deliveries
- Native SQL Server & PostgreSQL — Provider-specific implementations with pessimistic locking for safe multi-instance processing
- Health checks — Liveness (worker heartbeat) and readiness (DB connectivity) health checks
- Graceful shutdown — Drain in-flight deliveries before stopping
Installation
// Add core webhook services + delivery worker
builder.Services
.AddWebhooks(builder.Configuration)
.AddWebhooksSqlServer(builder.Configuration); // or AddWebhooksPostgreSql
Configuration
appsettings.json
{
"Webhooks": {
"Enabled": true,
"BatchSize": 50,
"MaxParallelism": 4,
"PollInterval": "00:00:02",
"InitialRetryDelay": "00:00:05",
"MaxRetryDelay": "00:05:00",
"DefaultMaxRetries": 5,
"DefaultTimeoutSeconds": 30,
"DrainOnShutdown": true,
"DrainTimeout": "00:00:30",
"HealthStaleAfter": "00:02:00",
"HttpRetryCount": 3,
"HttpRetryBaseDelay": "00:00:01",
"HttpTotalTimeout": "00:01:00",
"SqlServer": {
"ConnectionString": "Server=...;Database=...;",
"SubscriptionSchema": "dbo",
"SubscriptionTable": "WebhookSubscriptions",
"DeliverySchema": "dbo",
"DeliveryTable": "WebhookDeliveryAttempts",
"LeaseDuration": "00:02:00"
}
}
}
PostgreSQL variant
{
"Webhooks": {
"PostgreSql": {
"ConnectionString": "Host=...;Database=...;",
"SubscriptionSchema": "public",
"SubscriptionTable": "webhook_subscriptions",
"DeliverySchema": "public",
"DeliveryTable": "webhook_delivery_attempts",
"LeaseDuration": "00:02:00"
}
}
}
Usage
Dispatching Webhooks
public class OrderService(IWebhookDispatcher webhookDispatcher)
{
public async Task CreateOrderAsync(Order order, CancellationToken ct)
{
// ... create order logic ...
// Dispatch webhook to all active subscriptions for "order.created"
await webhookDispatcher.DispatchAsync(
eventType: "order.created",
payload: new { order.Id, order.Total, order.CreatedAt },
tenantId: order.TenantId, // optional
cancellationToken: ct);
}
}
Without Multi-Tenancy
await webhookDispatcher.DispatchAsync("user.registered", new { userId, email });
How It Works
1. DispatchAsync("order.created", payload, tenantId)
→ Queries active subscriptions matching event type + tenant
→ Snapshots each subscription's config into a WebhookDeliveryAttempt (Pending)
→ Persists to DeliveryAttempts table
2. WebhookDeliveryWorker (background service, polls every 2s):
→ Claims batch of pending deliveries (atomic lock: UPDLOCK/READPAST or FOR UPDATE SKIP LOCKED)
→ Sends HTTP request via resilience-enhanced HttpClient (immediate retries for transient failures)
→ Success (2xx) → MarkDelivered
→ Failure → MarkFailed with exponential backoff next attempt
→ Max retries exhausted → permanently Failed
Two-Level Resilience
| Level | Mechanism | Scope | Typical Duration |
|---|---|---|---|
| Immediate | Microsoft.Extensions.Http.Resilience (Polly v8) | Per HTTP call | Seconds (retry transient 5xx, network blips) |
| Durable | Outbox worker + exponential backoff | Across process restarts | Minutes to hours (extended outages) |
SQL Server Migration
-- Subscriptions table
CREATE TABLE [dbo].[WebhookSubscriptions]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY DEFAULT NEWID(),
[TenantId] NVARCHAR(200) NULL,
[EventType] NVARCHAR(200) NOT NULL,
[Name] NVARCHAR(200) NOT NULL,
[WebhookUrl] NVARCHAR(2000) NOT NULL,
[HttpMethod] NVARCHAR(10) NOT NULL DEFAULT 'POST',
[ContentType] NVARCHAR(100) NOT NULL DEFAULT 'application/json',
[Headers] NVARCHAR(MAX) NULL,
[TimeoutSeconds] INT NOT NULL DEFAULT 30,
[MaxRetries] INT NOT NULL DEFAULT 5,
[IsActive] BIT NOT NULL DEFAULT 1,
[CreatedAtUtc] DATETIMEOFFSET(7) NOT NULL DEFAULT SYSDATETIMEOFFSET(),
[UpdatedAtUtc] DATETIMEOFFSET(7) NULL
);
CREATE INDEX [IX_WebhookSubscriptions_EventType_IsActive]
ON [dbo].[WebhookSubscriptions] ([EventType], [IsActive])
INCLUDE ([TenantId]);
-- Delivery attempts table (outbox)
CREATE TABLE [dbo].[WebhookDeliveryAttempts]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY DEFAULT NEWID(),
[SubscriptionId] UNIQUEIDENTIFIER NOT NULL,
[TenantId] NVARCHAR(200) NULL,
[EventType] NVARCHAR(200) NOT NULL,
[WebhookUrl] NVARCHAR(2000) NOT NULL,
[HttpMethod] NVARCHAR(10) NOT NULL,
[ContentType] NVARCHAR(100) NOT NULL,
[Headers] NVARCHAR(MAX) NULL,
[TimeoutSeconds] INT NOT NULL DEFAULT 30,
[Payload] NVARCHAR(MAX) NOT NULL,
[State] INT NOT NULL DEFAULT 0,
[RetryCount] INT NOT NULL DEFAULT 0,
[MaxRetries] INT NOT NULL DEFAULT 5,
[NextAttemptAtUtc] DATETIMEOFFSET(7) NULL,
[LastError] NVARCHAR(MAX) NULL,
[ResponseStatusCode] INT NULL,
[CreatedAtUtc] DATETIMEOFFSET(7) NOT NULL DEFAULT SYSDATETIMEOFFSET(),
[DeliveredAtUtc] DATETIMEOFFSET(7) NULL,
[LockedUntilUtc] DATETIMEOFFSET(7) NULL
);
CREATE INDEX [IX_WebhookDeliveryAttempts_State_NextAttempt]
ON [dbo].[WebhookDeliveryAttempts] ([State], [NextAttemptAtUtc])
INCLUDE ([LockedUntilUtc], [CreatedAtUtc]);
PostgreSQL Migration
-- Subscriptions table
CREATE TABLE "public"."webhook_subscriptions"
(
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id VARCHAR(200) NULL,
event_type VARCHAR(200) NOT NULL,
name VARCHAR(200) NOT NULL,
webhook_url VARCHAR(2000) NOT NULL,
http_method VARCHAR(10) NOT NULL DEFAULT 'POST',
content_type VARCHAR(100) NOT NULL DEFAULT 'application/json',
headers JSONB NULL,
timeout_seconds INT NOT NULL DEFAULT 30,
max_retries INT NOT NULL DEFAULT 5,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at_utc TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at_utc TIMESTAMPTZ NULL
);
CREATE INDEX ix_webhook_subscriptions_event_type_active
ON "public"."webhook_subscriptions" (event_type, is_active)
INCLUDE (tenant_id);
-- Delivery attempts table (outbox)
CREATE TABLE "public"."webhook_delivery_attempts"
(
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
subscription_id UUID NOT NULL,
tenant_id VARCHAR(200) NULL,
event_type VARCHAR(200) NOT NULL,
webhook_url VARCHAR(2000) NOT NULL,
http_method VARCHAR(10) NOT NULL,
content_type VARCHAR(100) NOT NULL,
headers JSONB NULL,
timeout_seconds INT NOT NULL DEFAULT 30,
payload TEXT NOT NULL,
state INT NOT NULL DEFAULT 0,
retry_count INT NOT NULL DEFAULT 0,
max_retries INT NOT NULL DEFAULT 5,
next_attempt_at_utc TIMESTAMPTZ NULL,
last_error TEXT NULL,
response_status_code INT NULL,
created_at_utc TIMESTAMPTZ NOT NULL DEFAULT NOW(),
delivered_at_utc TIMESTAMPTZ NULL,
locked_until_utc TIMESTAMPTZ NULL
);
CREATE INDEX ix_webhook_delivery_attempts_state_next_attempt
ON "public"."webhook_delivery_attempts" (state, next_attempt_at_utc)
INCLUDE (locked_until_utc, created_at_utc);
Cleanup Recommendations
Old delivered/failed delivery attempts will accumulate over time. Consider running periodic cleanup:
-- SQL Server: Delete delivered attempts older than 30 days
DELETE FROM [dbo].[WebhookDeliveryAttempts]
WHERE [State] = 2 AND [DeliveredAtUtc] < DATEADD(DAY, -30, SYSDATETIMEOFFSET());
-- PostgreSQL: Delete delivered attempts older than 30 days
DELETE FROM "public"."webhook_delivery_attempts"
WHERE state = 2 AND delivered_at_utc < NOW() - INTERVAL '30 days';
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- Microsoft.Data.SqlClient (>= 6.1.4)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.3)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.3)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.3)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.3)
- Microsoft.Extensions.Http (>= 10.0.3)
- Microsoft.Extensions.Http.Resilience (>= 10.1.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.3)
- Microsoft.Extensions.Options (>= 10.0.3)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.3)
- Npgsql (>= 10.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.