Notavia.Sdk.AspNetCore
1.0.3
dotnet add package Notavia.Sdk.AspNetCore --version 1.0.3
NuGet\Install-Package Notavia.Sdk.AspNetCore -Version 1.0.3
<PackageReference Include="Notavia.Sdk.AspNetCore" Version="1.0.3" />
<PackageVersion Include="Notavia.Sdk.AspNetCore" Version="1.0.3" />
<PackageReference Include="Notavia.Sdk.AspNetCore" />
paket add Notavia.Sdk.AspNetCore --version 1.0.3
#r "nuget: Notavia.Sdk.AspNetCore, 1.0.3"
#:package Notavia.Sdk.AspNetCore@1.0.3
#addin nuget:?package=Notavia.Sdk.AspNetCore&version=1.0.3
#tool nuget:?package=Notavia.Sdk.AspNetCore&version=1.0.3
Notavia.Sdk.AspNetCore
ASP.NET Core Minimal API integration for receiving signed webhook events from Notavia.
Provides MapWebhookHandler — a single call that registers a POST endpoint, verifies the
HMAC-SHA256 signature on every request, and dispatches to your per-event callbacks.
Version: 1.0.2. Targets net8.0 and net10.0.
Install
dotnet add package Notavia.Sdk.AspNetCore
Requires Notavia.Sdk (pulled in automatically as a dependency).
Quick start
using NotifyService.Sdk.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapWebhookHandler("/webhooks/notify", new WebhookHandlerOptions
{
SigningSecret = builder.Configuration["NotifyService:WebhookSecret"]!,
OnNotificationSent = (evt, ct) =>
{
Console.WriteLine($"Sent: {evt.Id} type={evt.Type}");
return Task.CompletedTask;
},
OnNotificationFailed = (evt, ct) =>
{
Console.WriteLine($"Failed: {evt.Id}");
return Task.CompletedTask;
},
});
app.Run();
Configuration
The signing secret is shown once, when you create the endpoint under Settings → Webhooks in the Notavia console; it cannot be read back later, so store it as you would any other secret: Store it in user-secrets or an environment variable — never hard-code it.
dotnet user-secrets set "NotifyService:WebhookSecret" "whsec_YOUR_SECRET"
The secret may be provided as raw base64url or with the whsec_ prefix — both forms are accepted.
WebhookHandlerOptions reference
| Property | Type | Required | Description |
|---|---|---|---|
SigningSecret |
string |
Yes | Endpoint signing secret from the dashboard. |
Tolerance |
TimeSpan? |
No | Maximum age of the webhook timestamp. Defaults to 300 s (5 min). |
OnEvent |
callback | No | Fires for every verified event before the type-specific callback. Useful for logging or audit. |
OnNotificationSent |
callback | No | Fires when notification.sent is received. |
OnNotificationDelivered |
callback | No | Fires when notification.delivered is received. |
OnNotificationFailed |
callback | No | Fires when notification.failed is received. |
OnNotificationRead |
callback | No | Fires when notification.read is received. |
OnNotificationSuppressed |
callback | No | Fires when notification.suppressed is received. |
OnNotificationBounced |
callback | No | Fires when notification.bounced is received. |
OnNotificationComplained |
callback | No | Fires when notification.complained is received. |
OnProviderCritical |
callback | No | Fires when provider.critical is received — an email provider is failing your sends. Pro and above. |
OnProviderRecovered |
callback | No | Fires when provider.recovered is received. Pro and above. |
All callbacks have the signature Func<NotifyWebhookEvent<JsonElement>, CancellationToken, Task>.
JsonElement is the raw payload — deserialize to a typed model as needed.
An event type with no matching callback is still verified and still passed to OnEvent; it
is never an error. New event types can therefore be added without breaking your handler.
Event types
| Event type string | When it fires |
|---|---|
notification.sent |
The notification was accepted and queued for delivery. |
notification.delivered |
The channel provider confirmed delivery (email open/click or SMS delivery receipt). |
notification.failed |
All delivery attempts exhausted; the notification will not be retried. |
notification.read |
An inbox notification was marked read by the end-user. |
notification.suppressed |
Delivery was skipped because the end-user's preference suppresses this category/channel. |
notification.bounced |
Hard or soft bounce. The recipient is auto-suppressed on a hard bounce. |
notification.complained |
The recipient marked it as spam. Auto-suppressed. |
provider.critical |
One of your email providers is failing your sends right now. Pro and above. |
provider.recovered |
That provider is delivering again. Pro and above. |
Paging on a provider outage
provider.critical is the machine-readable half of provider alerting — wire it to your pager
and you will know a provider is down before your users tell you.
app.MapWebhookHandler("/webhooks/notify", new WebhookHandlerOptions
{
SigningSecret = builder.Configuration["Notify:WebhookSecret"]!,
OnProviderCritical = async (evt, ct) =>
{
ProviderAlert alert = evt.Data.Deserialize<ProviderAlert>()!;
await pager.PageAsync(
dedupeKey: evt.Id,
title: $"{alert.Provider} is failing {alert.FailureRate}% of our {alert.Environment} mail",
ct);
},
OnProviderRecovered = async (evt, ct) =>
{
ProviderAlert alert = evt.Data.Deserialize<ProviderAlert>()!;
await pager.ResolveAsync(title: $"{alert.Provider} recovered", ct);
},
});
public sealed record ProviderAlert(
[property: JsonPropertyName("provider")] string Provider,
[property: JsonPropertyName("environment")] string Environment,
[property: JsonPropertyName("messages")] long Messages,
[property: JsonPropertyName("failed_messages")] long FailedMessages,
[property: JsonPropertyName("failure_rate")] double FailureRate,
[property: JsonPropertyName("firing_since")] DateTimeOffset? FiringSince);
Two things worth knowing:
messagesandfailed_messagescount distinct messages, not delivery attempts. A single message the provider retried three times is one failed message, not three.- Dedupe on
evt.Id. It is stable for the life of one outage, so a retried webhook delivery carries the same id and will not page you twice for the same incident. A new outage on the same provider gets a new id.
HTTP response codes
| Code | Meaning |
|---|---|
200 OK |
Signature valid; all callbacks invoked. |
401 Unauthorized |
NotifyService-Signature header is missing or the HMAC check failed. |
400 Bad Request |
Signature valid but the body is not valid JSON. |
Receiving all events in one handler
Use OnEvent when you want a single callback regardless of type:
app.MapWebhookHandler("/webhooks/notify", new WebhookHandlerOptions
{
SigningSecret = config["NotifyService:WebhookSecret"]!,
OnEvent = (evt, ct) =>
{
logger.LogInformation("Webhook received: {Type} id={Id}", evt.Type, evt.Id);
return Task.CompletedTask;
},
});
OnEvent and type-specific callbacks are not mutually exclusive — both fire when both are set.
Error handling in callbacks
Exceptions thrown inside a callback propagate to ASP.NET Core's exception-handling pipeline.
Wrap your callback body in a try/catch or use the standard app.UseExceptionHandler middleware
if you want to suppress errors and still return 200 OK to Notavia.
Notavia retries delivery when it receives a non-2xx response, so returning 200 OK after
logging an error is the correct recovery pattern for transient processing failures.
Low-level signature verification
If you need to verify signatures outside of MapWebhookHandler (e.g., in a controller or
middleware), use WebhookSignatures.Verify from NotifyService.Sdk directly:
using NotifyService.Sdk;
bool valid = WebhookSignatures.Verify(
rawBody: rawBodyString,
signatureHeader: request.Headers["NotifyService-Signature"].ToString(),
signingSecret: config["NotifyService:WebhookSecret"]!);
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
-
net10.0
- Notavia.Sdk (>= 1.0.3)
-
net8.0
- Notavia.Sdk (>= 1.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.