RepletoryLib.Webhook.Core 1.0.0

dotnet add package RepletoryLib.Webhook.Core --version 1.0.0
                    
NuGet\Install-Package RepletoryLib.Webhook.Core -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.Core" 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.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="RepletoryLib.Webhook.Core" />
                    
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.Core --version 1.0.0
                    
#r "nuget: RepletoryLib.Webhook.Core, 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.Core@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.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=RepletoryLib.Webhook.Core&version=1.0.0
                    
Install as a Cake Tool

RepletoryLib.Webhook.Core

Webhook sender and receiver implementation with HMAC-SHA256 signature validation, HTTP delivery, retry logic, in-memory subscription management, and ASP.NET Core middleware for RepletoryLib.

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

NuGet .NET 10 License: MIT


Overview

RepletoryLib.Webhook.Core provides the default implementation of all webhook abstractions defined in RepletoryLib.Webhook.Abstractions. It includes:

  • HttpWebhookSender -- HTTP-based outbound webhook delivery with typed HttpClient, HMAC signature generation, custom headers, and bulk send support
  • HmacSignatureValidator -- HMAC-SHA256 signature generation and constant-time validation to prevent timing attacks
  • InMemoryWebhookSubscriptionManager -- Thread-safe, ConcurrentDictionary-backed subscription storage for development, testing, and single-instance deployments
  • WebhookReceiver -- Inbound webhook processor that validates signatures and dispatches events to registered handlers
  • WebhookEventRouter -- Routes incoming webhooks to matching IWebhookEventHandler<string> instances by event type or wildcard
  • WebhookReceiverMiddleware -- ASP.NET Core middleware that exposes a POST endpoint for receiving inbound webhooks with method, body size, and signature validation

One call to AddRepletoryWebhook(configuration) registers everything. One call to UseRepletoryWebhookReceiver() maps the receiver endpoint.

Key Features

  • Single-line DI registration -- AddRepletoryWebhook registers all services, options, and the typed HTTP client
  • Single-line endpoint mapping -- UseRepletoryWebhookReceiver maps the configurable POST endpoint
  • HMAC-SHA256 signatures -- Payloads are signed with sha256={hex} format using constant-time comparison
  • HTTP delivery -- Outbound webhooks delivered via typed HttpClient with configurable timeout and User-Agent
  • Bulk delivery -- Send to multiple endpoints with per-delivery result tracking
  • Retry support -- Retry previously failed deliveries with incremented attempt numbers
  • In-memory subscriptions -- Thread-safe subscription management with wildcard (*) event matching
  • Event routing -- Incoming webhooks automatically dispatched to matching IWebhookEventHandler<string> instances
  • Request validation -- Middleware enforces POST method, max body size (HTTP 413), and signature validation (HTTP 401)
  • Configurable options -- Extend WebhookOptions with WebhookCoreOptions for receiver path, body size limits, and User-Agent

Installation

dotnet add package RepletoryLib.Webhook.Core

Or add to your .csproj:

<PackageReference Include="RepletoryLib.Webhook.Core" 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.Webhook.Abstractions RepletoryLib
Microsoft.Extensions.Http NuGet
Microsoft.Extensions.Options.ConfigurationExtensions NuGet
Microsoft.AspNetCore.App Framework reference

Quick Start

Complete Program.cs Setup

using RepletoryLib.Webhook.Core;
using RepletoryLib.Webhook.Abstractions.Interfaces;

var builder = WebApplication.CreateBuilder(args);

// Register all webhook services (sender, receiver, signature validator,
// subscription manager, event router, and middleware)
builder.Services.AddRepletoryWebhook(builder.Configuration);

// Register your custom event handlers
builder.Services.AddScoped<IWebhookEventHandler<string>, OrderCreatedHandler>();
builder.Services.AddScoped<IWebhookEventHandler<string>, PaymentReceivedHandler>();

var app = builder.Build();

// Map the webhook receiver POST endpoint (default: /webhooks)
app.UseRepletoryWebhookReceiver();

app.Run();

appsettings.json

{
  "Webhook": {
    "SecretKey": "your-shared-secret-key",
    "SignatureHeaderName": "X-Webhook-Signature",
    "EventTypeHeaderName": "X-Webhook-Event",
    "RetryCount": 3,
    "RetryDelaySeconds": 5,
    "TimeoutSeconds": 30,
    "Core": {
      "ReceiverEndpointPath": "/webhooks",
      "MaxRequestBodySizeBytes": 1048576,
      "UserAgent": "RepletoryLib-Webhook/1.0"
    }
  }
}

Configuration

WebhookOptions (base)

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"

WebhookCoreOptions (extends WebhookOptions)

Property Type Default Description
ReceiverEndpointPath string "/webhooks" The endpoint path on which the webhook receiver listens for incoming requests
MaxRequestBodySizeBytes int 1048576 (1 MB) Maximum allowed request body size in bytes; requests exceeding this are rejected with HTTP 413
UserAgent string "RepletoryLib-Webhook/1.0" User-Agent header value used when sending outbound webhook requests

Section name: "Webhook:Core"


Usage Examples

DI Registration

using RepletoryLib.Webhook.Core;

// Register all webhook services with a single call
builder.Services.AddRepletoryWebhook(builder.Configuration);

This registers the following services:

Interface / Service Implementation Lifetime
IWebhookSender HttpWebhookSender Transient (via AddHttpClient)
IWebhookSignatureValidator HmacSignatureValidator Singleton
IWebhookSubscriptionManager InMemoryWebhookSubscriptionManager Singleton
IWebhookReceiver WebhookReceiver Scoped
WebhookEventRouter WebhookEventRouter Scoped
WebhookReceiverMiddleware WebhookReceiverMiddleware Scoped

Mapping the Receiver Endpoint

using RepletoryLib.Webhook.Core;

var app = builder.Build();

// Maps a POST endpoint at the configured path (default: /webhooks)
app.UseRepletoryWebhookReceiver();

app.Run();

The middleware handles:

  • 405 Method Not Allowed -- Non-POST requests
  • 413 Payload Too Large -- Body exceeds MaxRequestBodySizeBytes
  • 401 Unauthorized -- Invalid or missing signature (when SecretKey is configured)
  • 400 Bad Request -- Handler processing errors
  • 200 OK -- Webhook processed successfully (returns {"status":"ok"})

Sending Outbound Webhooks

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

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

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

    public async Task BroadcastAsync(string eventType, string data)
    {
        var subscribers = await _subscriptions.GetByEventTypeAsync(eventType);

        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);

        Console.WriteLine($"Delivered: {result.SuccessCount}, Failed: {result.FailureCount}");
    }
}

Implementing a Custom Event Handler

using RepletoryLib.Webhook.Abstractions.Interfaces;
using RepletoryLib.Webhook.Abstractions.Models.Inbound;
using System.Text.Json;

public class OrderCreatedHandler : IWebhookEventHandler<string>
{
    private readonly ILogger<OrderCreatedHandler> _logger;

    public OrderCreatedHandler(ILogger<OrderCreatedHandler> logger) => _logger = logger;

    public string EventType => "order.created";

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

        _logger.LogInformation(
            "Processing order {OrderId} received at {ReceivedAt} from {SourceIp}",
            order?.Id, webhook.ReceivedAt, webhook.SourceIp);

        // Process the order...
        await ProcessOrderAsync(order!, ct);
    }

    private Task ProcessOrderAsync(OrderDto order, CancellationToken ct)
    {
        // Your business logic here
        return Task.CompletedTask;
    }
}

public record OrderDto(string Id, string CustomerName, decimal Total);

Register the handler in Program.cs:

builder.Services.AddScoped<IWebhookEventHandler<string>, OrderCreatedHandler>();

Implementing Multiple Event Handlers

// Handler for payment events
public class PaymentReceivedHandler : IWebhookEventHandler<string>
{
    public string EventType => "payment.received";

    public async Task HandleAsync(string payload, IncomingWebhook webhook, CancellationToken ct = default)
    {
        var payment = JsonSerializer.Deserialize<PaymentDto>(payload);
        await UpdatePaymentStatusAsync(payment!);
    }

    private Task UpdatePaymentStatusAsync(PaymentDto payment) => Task.CompletedTask;
}

// Wildcard handler that logs all events
public class WebhookAuditHandler : IWebhookEventHandler<string>
{
    private readonly ILogger<WebhookAuditHandler> _logger;

    public WebhookAuditHandler(ILogger<WebhookAuditHandler> logger) => _logger = logger;

    public string EventType => "*";

    public async Task HandleAsync(string payload, IncomingWebhook webhook, CancellationToken ct = default)
    {
        _logger.LogInformation(
            "Audit: EventType={EventType}, SourceIp={SourceIp}, ReceivedAt={ReceivedAt}, BodyLength={BodyLength}",
            webhook.EventType, webhook.SourceIp, webhook.ReceivedAt, webhook.Body.Length);

        await Task.CompletedTask;
    }
}

// Register all handlers
builder.Services.AddScoped<IWebhookEventHandler<string>, OrderCreatedHandler>();
builder.Services.AddScoped<IWebhookEventHandler<string>, PaymentReceivedHandler>();
builder.Services.AddScoped<IWebhookEventHandler<string>, WebhookAuditHandler>();

HMAC Signature Validation

The HmacSignatureValidator generates and validates HMAC-SHA256 signatures in the format sha256={hex}:

using RepletoryLib.Webhook.Abstractions.Interfaces;

public class SignatureExample
{
    private readonly IWebhookSignatureValidator _validator;

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

    public void Demo()
    {
        var payload = "{\"event\":\"order.created\",\"data\":{\"id\":\"123\"}}";
        var secret = "my-secret-key";

        // Generate a signature
        string signature = _validator.GenerateSignature(payload, secret);
        // Result: "sha256=a1b2c3d4..."

        // Validate a signature (uses constant-time comparison)
        bool isValid = _validator.ValidateSignature(payload, signature, secret);
        // Result: true
    }
}

Managing Subscriptions

The InMemoryWebhookSubscriptionManager uses a ConcurrentDictionary for thread-safe subscription storage:

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

public class SubscriptionController
{
    private readonly IWebhookSubscriptionManager _manager;

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

    public async Task ManageSubscriptionsAsync()
    {
        // Create a subscription for a specific event type
        var sub = await _manager.SubscribeAsync(new WebhookSubscription
        {
            Url = "https://partner.example.com/webhooks",
            EventType = "order.created",
            Secret = "partner-shared-secret"
        });

        Console.WriteLine($"Subscription created: {sub.Id}");

        // Create a wildcard subscription
        await _manager.SubscribeAsync(new WebhookSubscription
        {
            Url = "https://audit.example.com/webhooks",
            EventType = "*",
            Secret = "audit-secret"
        });

        // Get all subscriptions for an event type (includes wildcard matches)
        var subs = await _manager.GetByEventTypeAsync("order.created");
        Console.WriteLine($"Matching subscriptions: {subs.Count}");

        // Deactivate a subscription
        await _manager.SetActiveAsync(sub.Id, isActive: false);

        // Retrieve a subscription by ID
        var fetched = await _manager.GetByIdAsync(sub.Id);
        Console.WriteLine($"Active: {fetched?.IsActive}"); // false

        // List all subscriptions
        var all = await _manager.GetAllAsync();
        Console.WriteLine($"Total subscriptions: {all.Count}");

        // Remove a subscription
        await _manager.UnsubscribeAsync(sub.Id);
    }
}

Retrying Failed Deliveries

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

public class WebhookRetryService
{
    private readonly IWebhookSender _sender;
    private readonly ILogger<WebhookRetryService> _logger;

    public WebhookRetryService(IWebhookSender sender, ILogger<WebhookRetryService> logger)
    {
        _sender = sender;
        _logger = logger;
    }

    public async Task<WebhookDeliveryResult> SendWithRetryAsync(
        WebhookDeliveryRequest request, int maxRetries = 3)
    {
        var result = await _sender.SendAsync(request);

        while (!result.Success && request.AttemptNumber < maxRetries)
        {
            request.AttemptNumber++;

            _logger.LogInformation(
                "Retrying webhook to {Url} (attempt {Attempt}/{Max})",
                request.Url, request.AttemptNumber, maxRetries);

            result = await _sender.RetryAsync(request);
        }

        return result;
    }
}

Complete End-to-End Example

Program.cs:

using RepletoryLib.Webhook.Core;
using RepletoryLib.Webhook.Abstractions.Interfaces;

var builder = WebApplication.CreateBuilder(args);

// Register webhook services
builder.Services.AddRepletoryWebhook(builder.Configuration);

// Register event handlers
builder.Services.AddScoped<IWebhookEventHandler<string>, OrderCreatedHandler>();
builder.Services.AddScoped<IWebhookEventHandler<string>, PaymentReceivedHandler>();
builder.Services.AddScoped<IWebhookEventHandler<string>, WebhookAuditHandler>();

var app = builder.Build();

// Map the webhook receiver endpoint
app.UseRepletoryWebhookReceiver();

// Example: register a subscription on startup
using (var scope = app.Services.CreateScope())
{
    var manager = scope.ServiceProvider.GetRequiredService<IWebhookSubscriptionManager>();

    await manager.SubscribeAsync(new RepletoryLib.Webhook.Abstractions.Models.Outbound.WebhookSubscription
    {
        Url = "https://partner.example.com/webhooks",
        EventType = "order.created",
        Secret = "partner-secret"
    });
}

app.Run();

appsettings.json:

{
  "Webhook": {
    "SecretKey": "my-webhook-signing-secret",
    "SignatureHeaderName": "X-Webhook-Signature",
    "EventTypeHeaderName": "X-Webhook-Event",
    "RetryCount": 3,
    "RetryDelaySeconds": 5,
    "TimeoutSeconds": 30,
    "Core": {
      "ReceiverEndpointPath": "/api/webhooks",
      "MaxRequestBodySizeBytes": 2097152,
      "UserAgent": "MyApp-Webhook/1.0"
    }
  }
}

API Reference

Extension Methods

ServiceCollectionExtensions
Method Returns Description
AddRepletoryWebhook(services, configuration) IServiceCollection Registers all webhook core services, options, and the typed HTTP client
WebApplicationExtensions
Method Returns Description
UseRepletoryWebhookReceiver(app) WebApplication Maps the webhook receiver POST endpoint at the configured path

Services

HttpWebhookSender

Implements IWebhookSender. Delivers webhook payloads via typed HttpClient with:

  • JSON serialization of WebhookPayload
  • HMAC-SHA256 signature generation when Secret is provided on the request
  • Event type header (X-Webhook-Event) set automatically
  • Custom headers from WebhookDeliveryRequest.Headers forwarded to the target
  • Configurable timeout from WebhookOptions.TimeoutSeconds
  • Configurable User-Agent from WebhookCoreOptions.UserAgent
HmacSignatureValidator

Implements IWebhookSignatureValidator. Generates and validates HMAC-SHA256 signatures:

  • Signature format: sha256={lowercase-hex}
  • Uses HMACSHA256.HashData for signature generation
  • Uses CryptographicOperations.FixedTimeEquals for constant-time comparison to prevent timing attacks
InMemoryWebhookSubscriptionManager

Implements IWebhookSubscriptionManager. Thread-safe subscription storage using ConcurrentDictionary:

  • Assigns new Guid ID and CreatedAt timestamp on SubscribeAsync
  • GetByEventTypeAsync returns subscriptions matching the event type or wildcard (*)
  • Suitable for development, testing, and single-instance deployments
  • For production multi-instance deployments, implement IWebhookSubscriptionManager with a persistent store
WebhookReceiver

Implements IWebhookReceiver. Processes incoming webhooks:

  • Validates signature when SecretKey is configured in WebhookOptions
  • Returns WebhookValidationResult.Failed("Missing signature") when signature is required but absent
  • Returns WebhookValidationResult.Failed("Invalid signature") when signature does not match
  • Dispatches to WebhookEventRouter after successful validation
  • Catches handler exceptions and returns failure with SignatureValid = true
WebhookEventRouter

Routes incoming webhooks to registered IWebhookEventHandler<string> instances:

  • Resolves handlers from DI via IServiceProvider
  • Matches handlers by exact event type or wildcard (*)
  • Invokes all matching handlers sequentially
WebhookReceiverMiddleware

ASP.NET Core middleware for the webhook receiver endpoint:

  • Validates request method is POST (405 otherwise)
  • Validates body size against MaxRequestBodySizeBytes (413 otherwise)
  • Reads body, extracts signature and event type from configured headers
  • Constructs IncomingWebhook with body, signature, headers, timestamp, and source IP
  • Delegates to IWebhookReceiver.ProcessAsync
  • Returns JSON responses: {"status":"ok"} on success, {"error":"..."} on failure

Options

WebhookCoreOptions

Extends WebhookOptions with receiver-specific and HTTP client settings.

Property Type Default Description
ReceiverEndpointPath string "/webhooks" POST endpoint path for the webhook receiver
MaxRequestBodySizeBytes int 1048576 (1 MB) Maximum allowed request body size; rejects with HTTP 413
UserAgent string "RepletoryLib-Webhook/1.0" User-Agent header for outbound requests

Section name: "Webhook:Core"


Integration with Other RepletoryLib Packages

Package Relationship
RepletoryLib.Webhook.Abstractions Direct dependency -- interfaces, models, enums, and base options
RepletoryLib.Common Transitive dependency -- shared base types

Testing

using NSubstitute;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RepletoryLib.Webhook.Abstractions.Interfaces;
using RepletoryLib.Webhook.Abstractions.Models.Inbound;
using RepletoryLib.Webhook.Abstractions.Options;
using RepletoryLib.Webhook.Core.Services;

public class WebhookReceiverTests
{
    [Fact]
    public async Task ProcessAsync_returns_success_when_signature_is_valid()
    {
        // Arrange
        var validator = new HmacSignatureValidator();
        var secret = "test-secret";
        var body = "{\"event\":\"order.created\"}";
        var signature = validator.GenerateSignature(body, secret);

        var options = Options.Create(new WebhookOptions { SecretKey = secret });
        var router = Substitute.For<WebhookEventRouter>(
            Substitute.For<IServiceProvider>(),
            Substitute.For<ILogger<WebhookEventRouter>>());

        var receiver = new WebhookReceiver(
            validator, router, options, Substitute.For<ILogger<WebhookReceiver>>());

        var webhook = new IncomingWebhook
        {
            EventType = "order.created",
            Body = body,
            Signature = signature
        };

        // Act
        var result = await receiver.ProcessAsync(webhook);

        // Assert
        result.Success.Should().BeTrue();
        result.SignatureValid.Should().BeTrue();
        result.EventType.Should().Be("order.created");
    }

    [Fact]
    public async Task ProcessAsync_returns_failure_when_signature_is_invalid()
    {
        // Arrange
        var validator = new HmacSignatureValidator();
        var options = Options.Create(new WebhookOptions { SecretKey = "correct-secret" });
        var router = Substitute.For<WebhookEventRouter>(
            Substitute.For<IServiceProvider>(),
            Substitute.For<ILogger<WebhookEventRouter>>());

        var receiver = new WebhookReceiver(
            validator, router, options, Substitute.For<ILogger<WebhookReceiver>>());

        var webhook = new IncomingWebhook
        {
            EventType = "order.created",
            Body = "{\"event\":\"order.created\"}",
            Signature = "sha256=invalid"
        };

        // Act
        var result = await receiver.ProcessAsync(webhook);

        // Assert
        result.Success.Should().BeFalse();
        result.SignatureValid.Should().BeFalse();
        result.Error.Should().Be("Invalid signature");
    }
}

public class HmacSignatureValidatorTests
{
    [Fact]
    public void GenerateSignature_returns_sha256_prefixed_hex()
    {
        var validator = new HmacSignatureValidator();
        var signature = validator.GenerateSignature("test-payload", "test-secret");

        signature.Should().StartWith("sha256=");
    }

    [Fact]
    public void ValidateSignature_returns_true_for_matching_signature()
    {
        var validator = new HmacSignatureValidator();
        var payload = "{\"event\":\"test\"}";
        var secret = "my-secret";

        var signature = validator.GenerateSignature(payload, secret);
        var isValid = validator.ValidateSignature(payload, signature, secret);

        isValid.Should().BeTrue();
    }

    [Fact]
    public void ValidateSignature_returns_false_for_wrong_secret()
    {
        var validator = new HmacSignatureValidator();
        var payload = "{\"event\":\"test\"}";

        var signature = validator.GenerateSignature(payload, "correct-secret");
        var isValid = validator.ValidateSignature(payload, signature, "wrong-secret");

        isValid.Should().BeFalse();
    }
}

Troubleshooting

Issue Solution
Receiver returns 405 Ensure the client sends a POST request to the configured ReceiverEndpointPath
Receiver returns 413 The request body exceeds MaxRequestBodySizeBytes; increase the limit or reduce the payload size
Receiver returns 401 The signature is missing or invalid; verify that the sender uses the same SecretKey and SignatureHeaderName
Receiver returns 400 An event handler threw an exception; check application logs for the handler error
Outbound delivery times out Increase TimeoutSeconds in WebhookOptions or investigate the target endpoint's response time
IWebhookEventHandler<string> not invoked Ensure the handler is registered in DI and its EventType property matches the incoming event type or "*"
Subscriptions lost after restart InMemoryWebhookSubscriptionManager does not persist data; implement IWebhookSubscriptionManager with a database for production

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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 66 3/2/2026