English | Português (Brasil)
ReliableWebhooks is a .NET 10 library for reliable outbound webhook delivery. It provides explicit primitives for durable enqueueing, lease-based dispatch, bounded concurrency, HTTP delivery, retry classification and scheduling, HMAC-SHA256 signing, observability, and Microsoft dependency injection/hosting.
Use it when an HTTP POST is not enough: deliveries must survive failures, coordinate multiple workers, retry deterministically, and expose operational state without pretending that exactly-once HTTP delivery exists.
- With a conforming durable
IWebhookDeliveryStore, the intended delivery model is at-least-once. - ReliableWebhooks does not guarantee exactly-once delivery. Receivers must be idempotent and tolerate duplicates.
- The core package does not ship a production durable store. Durability across process restarts depends on the store supplied and configured by the consuming application.
InMemoryWebhookDeliveryStoreis process-local, not durable, and intended only for tests, samples, and local development.- One
IWebhookDeliveryTransport.SendAsynccall represents one HTTP attempt; retry timing is handled separately by the retry policy and dispatcher. - Automatic dead-letter replay, receiver-side idempotency, and secret-rotation policy remain application responsibilities.
For the complete production contract and operational guidance, see Production usage and the Persistence contract.
The package targets net10.0:
dotnet add package ReliableWebhooks --version 1.0.0or:
<PackageReference Include="ReliableWebhooks" Version="1.0.0" />This minimal example uses the in-memory store so it can run without external infrastructure. Replace it with a conforming durable IWebhookDeliveryStore before using ReliableWebhooks in production.
using System.Text.Json;
using ReliableWebhooks;
byte[] payload = JsonSerializer.SerializeToUtf8Bytes(new
{
OrderId = 123,
Status = "created",
});
var message = new WebhookMessage(
id: Guid.NewGuid().ToString("N"),
eventType: "order.created",
destination: new Uri("https://example.com/webhooks"),
payload: payload,
contentType: "application/json");
IWebhookDeliveryStore store = new InstrumentedWebhookDeliveryStore(
new InMemoryWebhookDeliveryStore());
await store.EnqueueAsync(message, DateTimeOffset.UtcNow);
using var handler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = false,
};
using var httpClient = new HttpClient(handler);
IWebhookDeliveryTransport transport = new WebhookHttpTransport(httpClient);
IWebhookRetryPolicy retryPolicy = new DefaultWebhookRetryPolicy();
var dispatcher = new WebhookDispatcher(
store,
transport,
retryPolicy,
new WebhookDispatcherOptions
{
MaxConcurrency = 4,
LeaseDuration = TimeSpan.FromMinutes(1),
PollInterval = TimeSpan.FromSeconds(1),
ShutdownGracePeriod = TimeSpan.FromSeconds(5),
});
using var stop = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await dispatcher.RunAsync(stop.Token);AddHostedDispatcher() is opt-in. Applications can instead resolve and run WebhookDispatcher directly when they need to own its lifecycle.
A runnable end-to-end application with signing, success, retries, dead-letter behavior, and diagnostics is available in samples/ReliableWebhooks.Sample.
- Create a
WebhookMessagewith a stable ID, event type, destination, exact payload bytes, content type, and optional headers. - Enqueue it through
IWebhookEnqueueServiceor the lower-levelIWebhookDeliveryStore. WebhookDispatcherclaims only due work up to its available concurrency and receives expiring leases.WebhookHttpTransportperforms one request attempt and returns a transport-neutral result.- Success and permanent failure become terminal states; retryable failures are scheduled by
IWebhookRetryPolicy. - A conforming durable store persists the resulting state so expired or abandoned work can be reclaimed safely.
Stable IDs make enqueueing idempotent at the store boundary, while leases prevent stale workers from overwriting the current owner. These mechanisms support at-least-once delivery; they do not remove the need for receiver idempotency.
A production application owns the persistence adapter and explicitly registers it:
services.AddSingleton<IWebhookDeliveryStore, MyDurableWebhookStore>();
ReliableWebhooksBuilder webhooks = services.AddReliableWebhooks();
webhooks.AddHostedDispatcher();The store must implement the durability, atomic claim, lease, stale-owner rejection, retry scheduling, and terminal-state semantics defined by the persistence contract.
- Production usage:
docs/production-usage.md— DI/hosting, retry and response classification, signing and receiver verification, destination security, observability, configuration, tuning, and troubleshooting. - Persistence contract:
docs/persistence.md— durable state, idempotent enqueue, atomic claims, leases, ownership, transitions, and conformance testing. - Runnable sample:
samples/ReliableWebhooks.Sample— end-to-end integration using the public API. - v1.0.0 scope:
docs/release-v1.0.0.md— release boundaries, distribution, and supported surface. - Security policy:
SECURITY.md— supported security reporting process and project security guidance.
The core behaviors are exposed through replaceable abstractions, including persistence, delivery transport, response classification, retry policy/jitter, request signing and secret resolution, destination policy, request-header resolution, dispatcher timing, and observability integration. The production guide documents the built-in defaults and the boundaries that custom implementations must preserve.
See CONTRIBUTING.md for local development, validation, and contribution guidance.
ReliableWebhooks is licensed under the MIT License.