RepletoryLib.Webhook.Abstractions 1.0.0

dotnet add package RepletoryLib.Webhook.Abstractions --version 1.0.0
                    
NuGet\Install-Package RepletoryLib.Webhook.Abstractions -Version 1.0.0
                    
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="RepletoryLib.Webhook.Abstractions" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RepletoryLib.Webhook.Abstractions" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="RepletoryLib.Webhook.Abstractions" />
                    
Project file
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 RepletoryLib.Webhook.Abstractions --version 1.0.0
                    
#r "nuget: RepletoryLib.Webhook.Abstractions, 1.0.0"
                    
#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 RepletoryLib.Webhook.Abstractions@1.0.0
                    
#: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=RepletoryLib.Webhook.Abstractions&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=RepletoryLib.Webhook.Abstractions&version=1.0.0
                    
Install as a Cake Tool

RepletoryLib.Webhook.Abstractions

Webhook abstractions and interfaces for inbound receiving, outbound delivery, HMAC signature validation, and subscription management.

Part of the RepletoryLib ecosystem -- standalone, reusable .NET 10 libraries with zero business logic.

NuGet .NET 10 License: MIT


Overview

RepletoryLib.Webhook.Abstractions defines the core contracts, models, enums, and options for both outbound webhook delivery and inbound webhook reception. It provides IWebhookSender for delivering payloads to subscriber endpoints, IWebhookSubscriptionManager for managing subscriptions, IWebhookReceiver for processing incoming webhooks, IWebhookEventHandler<TPayload> for handling specific event types, and IWebhookSignatureValidator for HMAC signature generation and validation.

This package contains no implementations -- it is the abstraction layer that concrete providers implement. Reference this package in your application code to program against interfaces, then swap implementations without changing business logic.

Key Features

  • IWebhookSender -- Outbound webhook delivery: single send, bulk send, and retry operations
  • IWebhookSubscriptionManager -- Full subscription lifecycle: create, remove, retrieve by ID or event type, list all, and activate/deactivate
  • IWebhookReceiver -- Inbound webhook processing: signature validation and event dispatching
  • IWebhookEventHandler<TPayload> -- Strongly-typed event handler contract for processing specific webhook event types
  • IWebhookSignatureValidator -- Signature generation and validation for payload integrity and authenticity
  • WebhookPayload / WebhookDeliveryRequest -- Outbound models with event type, data, metadata, secrets, and custom headers
  • WebhookDeliveryResult / BulkDeliveryResult -- Delivery outcome models with per-delivery success/failure tracking
  • WebhookSubscription -- Subscription model with URL, event type, secret, active state, and metadata
  • IncomingWebhook / WebhookValidationResult -- Inbound models with body, signature, headers, source IP, and validation outcomes
  • WebhookDeliveryStatus enum -- Delivery state tracking: Pending, Delivered, Failed, Retrying
  • WebhookEventType constants -- Well-known event types including wildcard (*) and ping
  • WebhookOptions -- Shared configuration for secrets, headers, retry counts, delays, and timeouts

Installation

dotnet add package RepletoryLib.Webhook.Abstractions

Or add to your .csproj:

<PackageReference Include="RepletoryLib.Webhook.Abstractions" Version="1.0.0" />

Note: RepletoryLib packages are published to a local BaGet feed. See the main repository README for feed configuration.

Dependencies

Package Type
RepletoryLib.Common RepletoryLib

Quick Start

1. Reference the abstractions in your service layer

using RepletoryLib.Webhook.Abstractions.Interfaces;
using RepletoryLib.Webhook.Abstractions.Models.Outbound;

public class OrderWebhookService
{
    private readonly IWebhookSender _sender;
    private readonly IWebhookSubscriptionManager _subscriptions;

    public OrderWebhookService(IWebhookSender sender, IWebhookSubscriptionManager subscriptions)
    {
        _sender = sender;
        _subscriptions = subscriptions;
    }

    public async Task NotifyOrderCreatedAsync(string orderId, string orderJson)
    {
        var subscribers = await _subscriptions.GetByEventTypeAsync("order.created");

        foreach (var sub in subscribers.Where(s => s.IsActive))
        {
            var request = new WebhookDeliveryRequest
            {
                Url = sub.Url,
                Secret = sub.Secret,
                Payload = new WebhookPayload
                {
                    EventType = "order.created",
                    Data = orderJson
                }
            };

            await _sender.SendAsync(request);
        }
    }
}

2. Register a concrete provider in Program.cs

using RepletoryLib.Webhook.Core;

builder.Services.AddRepletoryWebhook(builder.Configuration);

Configuration

WebhookOptions

Property Type Default Description
SecretKey string "" Secret key used to sign and validate webhook payloads
SignatureHeaderName string "X-Webhook-Signature" HTTP header name for transmitting the webhook signature
EventTypeHeaderName string "X-Webhook-Event" HTTP header name for transmitting the webhook event type
RetryCount int 3 Maximum number of retry attempts for failed deliveries
RetryDelaySeconds int 5 Delay in seconds between retry attempts
TimeoutSeconds int 30 Timeout in seconds for each webhook delivery HTTP request

Section name: "Webhook"

{
  "Webhook": {
    "SecretKey": "your-shared-secret-key",
    "SignatureHeaderName": "X-Webhook-Signature",
    "EventTypeHeaderName": "X-Webhook-Event",
    "RetryCount": 3,
    "RetryDelaySeconds": 5,
    "TimeoutSeconds": 30
  }
}

Usage Examples

Sending Outbound Webhooks

using RepletoryLib.Webhook.Abstractions.Interfaces;
using RepletoryLib.Webhook.Abstractions.Models.Outbound;

public class PaymentWebhookService
{
    private readonly IWebhookSender _sender;

    public PaymentWebhookService(IWebhookSender sender) => _sender = sender;

    public async Task<WebhookDeliveryResult> NotifyPaymentReceivedAsync(
        string endpointUrl, string secret, string paymentJson)
    {
        var request = new WebhookDeliveryRequest
        {
            Url = endpointUrl,
            Secret = secret,
            Payload = new WebhookPayload
            {
                EventType = "payment.received",
                Data = paymentJson,
                Metadata = new Dictionary<string, string>
                {
                    ["source"] = "payment-service"
                }
            },
            Headers = new Dictionary<string, string>
            {
                ["X-Correlation-Id"] = Guid.NewGuid().ToString()
            }
        };

        return await _sender.SendAsync(request);
    }
}

Bulk Delivery

public async Task<BulkDeliveryResult> BroadcastEventAsync(
    string eventType,
    string data,
    IEnumerable<WebhookSubscription> subscribers)
{
    var requests = subscribers.Where(s => s.IsActive).Select(sub => new WebhookDeliveryRequest
    {
        Url = sub.Url,
        Secret = sub.Secret,
        Payload = new WebhookPayload
        {
            EventType = eventType,
            Data = data
        }
    });

    var result = await _sender.SendBulkAsync(requests);

    // Partial success -- check individual results
    foreach (var r in result.Results.Where(r => !r.Success))
    {
        _logger.LogWarning("Delivery to {Url} failed: {Error}", r.Url, r.Error);
    }

    _logger.LogInformation("Bulk delivery: {Success}/{Total} succeeded",
        result.SuccessCount, result.Results.Count);

    return result;
}

Retrying Failed Deliveries

public async Task<WebhookDeliveryResult> RetryFailedDeliveryAsync(
    WebhookDeliveryRequest originalRequest)
{
    originalRequest.AttemptNumber++;
    return await _sender.RetryAsync(originalRequest);
}

Managing Subscriptions

using RepletoryLib.Webhook.Abstractions.Interfaces;
using RepletoryLib.Webhook.Abstractions.Models.Outbound;

public class SubscriptionService
{
    private readonly IWebhookSubscriptionManager _manager;

    public SubscriptionService(IWebhookSubscriptionManager manager) => _manager = manager;

    // Create a subscription
    public async Task<WebhookSubscription> RegisterAsync(string url, string eventType, string secret)
    {
        return await _manager.SubscribeAsync(new WebhookSubscription
        {
            Url = url,
            EventType = eventType,
            Secret = secret,
            Metadata = new Dictionary<string, string>
            {
                ["registered-by"] = "subscription-api"
            }
        });
    }

    // Subscribe to all events using wildcard
    public async Task<WebhookSubscription> RegisterForAllEventsAsync(string url, string secret)
    {
        return await _manager.SubscribeAsync(new WebhookSubscription
        {
            Url = url,
            EventType = "*",
            Secret = secret
        });
    }

    // Get all subscribers for a specific event type
    public async Task<IReadOnlyList<WebhookSubscription>> GetSubscribersAsync(string eventType)
    {
        return await _manager.GetByEventTypeAsync(eventType);
    }

    // Deactivate a subscription without deleting it
    public async Task DeactivateAsync(Guid subscriptionId)
    {
        await _manager.SetActiveAsync(subscriptionId, isActive: false);
    }

    // Remove a subscription
    public async Task RemoveAsync(Guid subscriptionId)
    {
        await _manager.UnsubscribeAsync(subscriptionId);
    }
}

Receiving Inbound Webhooks

using RepletoryLib.Webhook.Abstractions.Interfaces;
using RepletoryLib.Webhook.Abstractions.Models.Inbound;

public class ExternalWebhookService
{
    private readonly IWebhookReceiver _receiver;

    public ExternalWebhookService(IWebhookReceiver receiver) => _receiver = receiver;

    public async Task<WebhookValidationResult> HandleIncomingAsync(
        string body, string? signature, string eventType, Dictionary<string, string> headers)
    {
        var webhook = new IncomingWebhook
        {
            Body = body,
            Signature = signature,
            EventType = eventType,
            Headers = headers
        };

        var result = await _receiver.ProcessAsync(webhook);

        if (!result.Success)
        {
            if (!result.SignatureValid)
                Console.WriteLine("Rejected webhook: invalid signature");
            else
                Console.WriteLine($"Webhook processing failed: {result.Error}");
        }

        return result;
    }
}

Implementing a Custom Event Handler

using RepletoryLib.Webhook.Abstractions.Interfaces;
using RepletoryLib.Webhook.Abstractions.Models.Inbound;

public class OrderCreatedHandler : IWebhookEventHandler<string>
{
    public string EventType => "order.created";

    public async Task HandleAsync(string payload, IncomingWebhook webhook, CancellationToken ct = default)
    {
        // Deserialize and process the payload
        var order = JsonSerializer.Deserialize<OrderDto>(payload);

        Console.WriteLine($"Order {order?.Id} received at {webhook.ReceivedAt} from {webhook.SourceIp}");

        await Task.CompletedTask;
    }
}

// Register in DI
builder.Services.AddScoped<IWebhookEventHandler<string>, OrderCreatedHandler>();

Implementing a Wildcard Event Handler

public class AuditLogHandler : IWebhookEventHandler<string>
{
    public string EventType => "*";

    public async Task HandleAsync(string payload, IncomingWebhook webhook, CancellationToken ct = default)
    {
        Console.WriteLine(
            "Webhook received: EventType={0}, SourceIp={1}, ReceivedAt={2}",
            webhook.EventType, webhook.SourceIp, webhook.ReceivedAt);

        await _auditRepository.LogAsync(webhook.EventType, payload, webhook.ReceivedAt);
    }
}

Generating and Validating Signatures

using RepletoryLib.Webhook.Abstractions.Interfaces;

public class SignatureService
{
    private readonly IWebhookSignatureValidator _validator;

    public SignatureService(IWebhookSignatureValidator validator) => _validator = validator;

    public string SignPayload(string payload, string secret)
    {
        return _validator.GenerateSignature(payload, secret);
    }

    public bool VerifyPayload(string payload, string signature, string secret)
    {
        return _validator.ValidateSignature(payload, signature, secret);
    }
}

Working with WebhookDeliveryResult

using RepletoryLib.Webhook.Abstractions.Enums;
using RepletoryLib.Webhook.Abstractions.Models.Outbound;

// Factory methods
var success = WebhookDeliveryResult.Succeeded(
    url: "https://example.com/webhooks",
    webhookId: Guid.NewGuid(),
    httpStatusCode: 200,
    attemptNumber: 1);

var failure = WebhookDeliveryResult.Failed(
    url: "https://example.com/webhooks",
    webhookId: Guid.NewGuid(),
    error: "Connection refused",
    attemptNumber: 3,
    httpStatusCode: null);

// Inspect results
if (success.Success)
    Console.WriteLine($"Delivered to {success.Url} on attempt {success.AttemptNumber}");

if (!failure.Success)
    Console.WriteLine($"Failed ({failure.Status}): {failure.Error}");

// BulkDeliveryResult aggregation
var bulk = new BulkDeliveryResult { Results = [success, failure] };
Console.WriteLine($"Total: {bulk.Results.Count}, OK: {bulk.SuccessCount}, Failed: {bulk.FailureCount}");

Working with WebhookValidationResult

using RepletoryLib.Webhook.Abstractions.Models.Inbound;

// Factory methods
var valid = WebhookValidationResult.Succeeded("order.created");
var invalid = WebhookValidationResult.Failed("Invalid signature", signatureValid: false);
var handlerError = WebhookValidationResult.Failed("Handler threw exception", signatureValid: true);

// Inspect results
if (valid.Success)
    Console.WriteLine($"Processed event: {valid.EventType}");

if (!invalid.Success && !invalid.SignatureValid)
    Console.WriteLine($"Signature rejected: {invalid.Error}");

API Reference

Interfaces

IWebhookSender
Method Returns Description
SendAsync(request, ct) Task<WebhookDeliveryResult> Sends a single webhook delivery request to the target endpoint
SendBulkAsync(requests, ct) Task<BulkDeliveryResult> Sends multiple webhook delivery requests in bulk
RetryAsync(request, ct) Task<WebhookDeliveryResult> Retries a previously failed webhook delivery request
IWebhookSubscriptionManager
Method Returns Description
SubscribeAsync(subscription, ct) Task<WebhookSubscription> Creates or registers a new webhook subscription
UnsubscribeAsync(subscriptionId, ct) Task Removes an existing webhook subscription
GetByIdAsync(subscriptionId, ct) Task<WebhookSubscription?> Retrieves a subscription by its unique identifier
GetByEventTypeAsync(eventType, ct) Task<IReadOnlyList<WebhookSubscription>> Retrieves all subscriptions matching the specified event type
GetAllAsync(ct) Task<IReadOnlyList<WebhookSubscription>> Retrieves all registered webhook subscriptions
SetActiveAsync(subscriptionId, isActive, ct) Task Sets the active state of a webhook subscription
IWebhookReceiver
Method Returns Description
ProcessAsync(webhook, ct) Task<WebhookValidationResult> Processes an incoming webhook by validating its signature and dispatching the event
IWebhookEventHandler<TPayload>
Member Type Description
EventType string (property) The event type this handler processes
HandleAsync(payload, webhook, ct) Task Handles the deserialized webhook payload
IWebhookSignatureValidator
Method Returns Description
GenerateSignature(payload, secret) string Generates a cryptographic signature for the payload
ValidateSignature(payload, signature, secret) bool Validates a signature against the expected value

Outbound Models

WebhookPayload
Property Type Default Description
Id Guid Guid.NewGuid() Unique identifier for this webhook payload
EventType string (required) The type of event this payload represents
Data string (required) The serialized event data
Timestamp DateTime DateTime.UtcNow UTC timestamp when the payload was created
Metadata Dictionary<string, string>? null Optional metadata key-value pairs
WebhookDeliveryRequest
Property Type Default Description
Url string (required) Target URL for webhook delivery
Payload WebhookPayload (required) The webhook payload to deliver
Secret string? null Secret for signature generation; no signature when null
Headers Dictionary<string, string>? null Optional HTTP headers to include in the delivery
AttemptNumber int 1 Current delivery attempt number
WebhookDeliveryResult
Property Type Description
Success bool Whether the delivery was successful
Status WebhookDeliveryStatus The delivery status
HttpStatusCode int? HTTP status code from the target; null if no response received
Error string? Error message for failed deliveries
Url string Target URL the webhook was delivered to
WebhookId Guid Unique identifier of the delivered webhook
AttemptNumber int Attempt number for this delivery result
AttemptedAt DateTime UTC timestamp of the delivery attempt
Static Method Returns Description
Succeeded(url, webhookId, httpStatusCode, attemptNumber) WebhookDeliveryResult Creates a successful delivery result
Failed(url, webhookId, error, attemptNumber, httpStatusCode?) WebhookDeliveryResult Creates a failed delivery result
BulkDeliveryResult
Property Type Description
Results List<WebhookDeliveryResult> Individual delivery results for each webhook
SuccessCount int Number of successfully delivered webhooks (computed)
FailureCount int Number of failed deliveries (computed)
WebhookSubscription
Property Type Default Description
Id Guid Guid.NewGuid() Unique identifier for this subscription
Url string (required) Target URL for webhook deliveries
EventType string (required) Event type to listen for; use "*" for all events
Secret string? null Secret for signing payloads; no signature when null
IsActive bool true Whether this subscription receives deliveries
CreatedAt DateTime DateTime.UtcNow UTC creation timestamp
Metadata Dictionary<string, string>? null Optional metadata key-value pairs

Inbound Models

IncomingWebhook
Property Type Default Description
EventType string (required) The event type of the incoming webhook
Body string (required) The raw body content of the webhook request
Signature string? null The signature for verification; null when not provided
Headers Dictionary<string, string> new() HTTP headers received with the webhook request
ReceivedAt DateTime DateTime.UtcNow UTC timestamp when the webhook was received
SourceIp string? null Source IP address of the webhook sender
WebhookValidationResult
Property Type Description
Success bool Whether the webhook was processed successfully
SignatureValid bool Whether the webhook signature was valid
Error string? Error message when validation or processing fails
EventType string? Event type extracted from the incoming webhook
Static Method Returns Description
Succeeded(eventType) WebhookValidationResult Creates a successful validation result
Failed(error, signatureValid?) WebhookValidationResult Creates a failed validation result

Enums

WebhookDeliveryStatus
Value Description
Pending Delivery is queued and has not yet been attempted
Delivered Webhook was delivered successfully to the target endpoint
Failed Delivery failed and no further retries will be attempted
Retrying Delivery failed but is scheduled for a retry attempt
WebhookEventType (static constants)
Constant Value Description
All "*" Wildcard event type that matches all events
Ping "ping" Lightweight event type for verifying webhook connectivity

Integration with Other RepletoryLib Packages

Package Relationship
RepletoryLib.Common Direct dependency -- shared base types
RepletoryLib.Webhook.Core Default implementation of all webhook abstractions with HTTP sender, HMAC validation, in-memory subscriptions, and ASP.NET Core middleware

Testing

using NSubstitute;
using RepletoryLib.Webhook.Abstractions.Interfaces;
using RepletoryLib.Webhook.Abstractions.Models.Outbound;

public class OrderWebhookServiceTests
{
    [Fact]
    public async Task NotifyOrderCreatedAsync_sends_to_active_subscribers()
    {
        // Arrange
        var sender = Substitute.For<IWebhookSender>();
        sender.SendAsync(Arg.Any<WebhookDeliveryRequest>(), Arg.Any<CancellationToken>())
            .Returns(WebhookDeliveryResult.Succeeded(
                "https://example.com/webhooks",
                Guid.NewGuid(),
                200,
                1));

        var manager = Substitute.For<IWebhookSubscriptionManager>();
        manager.GetByEventTypeAsync("order.created", Arg.Any<CancellationToken>())
            .Returns(new List<WebhookSubscription>
            {
                new() { Url = "https://example.com/webhooks", EventType = "order.created", IsActive = true }
            });

        var service = new OrderWebhookService(sender, manager);

        // Act
        await service.NotifyOrderCreatedAsync("order-123", "{\"id\":\"order-123\"}");

        // Assert
        await sender.Received(1).SendAsync(
            Arg.Is<WebhookDeliveryRequest>(r =>
                r.Url == "https://example.com/webhooks" &&
                r.Payload.EventType == "order.created"),
            Arg.Any<CancellationToken>());
    }
}

Troubleshooting

Issue Solution
WebhookDeliveryResult.Success is false Check the Error and HttpStatusCode properties for the failure reason; common causes are connection errors or non-2xx responses
Subscription not receiving events Verify that IsActive is true and that EventType matches the published event type (or use "*" for all events)
Signature validation fails Ensure the SecretKey in WebhookOptions matches between sender and receiver; check for whitespace or encoding differences
WebhookValidationResult.SignatureValid is false The signature is missing or does not match; verify the signing secret and header name configuration
No handlers invoked for incoming webhook Ensure IWebhookEventHandler<string> implementations are registered in DI and their EventType matches the incoming event

License

This project is licensed under the MIT License.

Copyright (c) 2024-2026 Repletory.


For complete documentation, infrastructure setup, and configuration reference, see the RepletoryLib main repository.

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

NuGet packages (1)

Showing the top 1 NuGet packages that depend on RepletoryLib.Webhook.Abstractions:

Package Downloads
RepletoryLib.Webhook.Core

Webhook sender and receiver implementation with HMAC signature validation and retry logic for RepletoryLib

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 87 3/2/2026