.NET SDK
Last updated
The Whisperr NuGet package sends events and identify calls from a .NET
backend: ASP.NET Core apps, workers and serverless handlers. Use it for facts
only your server knows, such as payment failures, cancellations and plan
changes, and to confirm contact consent with the server key.
Install
Section titled “Install”dotnet add package WhisperrThe package is Whisperr on
NuGet. The current version is 1.1.0. It targets net8.0 and netstandard2.0
(.NET Framework 4.7.2 and later).
Quickstart
Section titled “Quickstart”using Whisperr;
// WHISPERR_API_KEY is the server key (wrk_…).var whisperr = new WhisperrClient(Environment.GetEnvironmentVariable("WHISPERR_API_KEY")!);
// A server-side event:whisperr.Track("user_8842", "payment_failed", new Dictionary<string, object?>{ ["amount_cents"] = 4900, ["reason"] = "card_declined"});
// Traits and a contact the user agreed to:whisperr.Identify("user_8842", new IdentifyOptions{ Traits = new Dictionary<string, object?> { ["first_name"] = "Ada", ["plan"] = "pro" }, Channels = new[] { new WhisperrChannel(WhisperrChannelType.Email, "ada@example.com", optedIn: true, verified: true) }});
// Before the process exits:await whisperr.ShutdownAsync();Create the server key as a Server key in Settings → API keys. Hold one client for the life of the process.
Track(externalUserId, eventType, properties, context) always takes the user
id first. Pass the same id your app passes to identify(), and backend and
frontend events land on one timeline.
Event names must be lowercase snake_case. Track and Identify return
false when they do not queue the call, for example for an invalid event
name, which is also reported to OnError as dropped.
How do I confirm contact consent with the .NET SDK?
Section titled “How do I confirm contact consent with the .NET SDK?”The .NET SDK runs with the server key, so its Identify is how you confirm
consent. Pass optedIn: true only when the user agreed to messages on that
channel, and verified: true only for an email address your app confirmed.
Pass optedIn: false to record an opt-out.
See Contact consent.
ASP.NET Core
Section titled “ASP.NET Core”Install the companion package for dependency injection and middleware:
dotnet add package Whisperr.AspNetCoreWhisperr.AspNetCore
1.1.0 targets net8.0 and brings in the Whisperr package.
AddWhisperr binds the Whisperr configuration section, falls back to the
WHISPERR_API_KEY environment variable for the key, and registers
WhisperrClient as a singleton. The host disposes it on shutdown, which runs
a final flush.
using Whisperr.AspNetCore;
builder.Services.AddWhisperr(builder.Configuration, options =>{ options.OnError = error => Console.Error.WriteLine($"whisperr:{error.Code} {error.Message}");});{ "Whisperr": { "ApiKey": "wrk_your_server_key", "AppVersion": "2026.10.1", "FlushInterval": "00:00:10" }}Inject WhisperrClient wherever you have the user id. Track and Identify
return at once. Let the background flush send them; do not await
FlushAsync on the request path, because a flush can wait through retry
backoff.
app.MapPost("/billing/webhook", (BillingWebhook webhook, WhisperrClient whisperr) =>{ whisperr.Track(webhook.UserId, "payment_failed", new Dictionary<string, object?> { ["amount_cents"] = webhook.AmountCents, ["provider"] = "stripe" });
return Results.Ok();});Request-scoped tracking
Section titled “Request-scoped tracking”For endpoints with a signed-in user, add the middleware after authentication
and call HttpContext.Whisperr(). It uses the user id from the
NameIdentifier claim, then the sub claim. When no user is signed in, the
calls do nothing.
using Whisperr.AspNetCore;
app.UseAuthentication();app.UseWhisperr();// To read the user id from another claim:// app.UseWhisperr(http => http.User.FindFirst("user_id")?.Value);
app.MapPost("/plan/upgrade", (HttpContext http) =>{ http.Whisperr().Track("plan_upgraded", new Dictionary<string, object?> { ["plan"] = "pro" }); return Results.Ok();});HttpContext.Whisperr() throws InvalidOperationException when
UseWhisperr() is not in the pipeline.
How do I set the message language from .NET?
Section titled “How do I set the message language from .NET?”Pass the language the user picked in your product as the app_language
trait. Use a BCP 47 tag such as de or pt-BR. The .NET SDK has no separate
method for it and never guesses a language.
whisperr.Identify("user_8842", new IdentifyOptions{ Traits = new Dictionary<string, object?> { ["app_language"] = "pt-BR" }});What the .NET SDK adds to every event
Section titled “What the .NET SDK adds to every event”context key | Value |
|---|---|
sdk_name | whisperr-dotnet |
sdk_version | The package version, for example 1.1.0 |
app_version | The AppVersion option (Whisperr:AppVersion in configuration). Not set: no key. |
A key you pass in the context argument of Track wins over these.
How do I send billing events from .NET?
Section titled “How do I send billing events from .NET?”When Whisperr has no billing source connected for your app (such as
RevenueCat), send membership changes from your
backend with the reserved names in WhisperrBillingEvents. Never send them
from the browser or the app.
whisperr.Track("user_8842", WhisperrBillingEvents.MembershipStarted, new Dictionary<string, object?>{ ["plan_id"] = "pro_monthly", ["price"] = 9.99m, // major units, not cents ["currency"] = "USD", // ISO 4217, upper case ["plan_period"] = "P1M", // ISO 8601 duration ["transaction_id"] = "txn_123", ["store"] = "stripe", ["is_first_ever"] = true, ["access_source"] = "self_subscription", ["current_period_ends_at"] = "2026-11-12T10:00:00Z"});WhisperrBillingEvents has TrialStarted, TrialConverted,
TrialAutoRenewDisabled, MembershipStarted, RenewalSucceeded,
AutoRenewDisabled, AutoRenewEnabled, MembershipEnded,
BillingIssueStarted, BillingRecovered and RefundGranted. The
Node SDK page lists the
properties of each event and the closed value lists.
Register these event names for your app; setup with your AI agent registers the events your app sends. Whisperr stores an event with an unregistered name but does not map it to your app’s events.
How does the .NET SDK deliver events?
Section titled “How does the .NET SDK deliver events?”TrackandIdentifyadd to an in-memory queue and return at once.- Events go to
POST /v1/events/batchand identify calls toPOST /v1/identify, in order, with theX-API-Keyheader. - Every event has a
$message_idthat stays the same across retries, so Whisperr removes duplicates. 429,5xx, timeouts and network errors are retried with exponential backoff, up to 30 seconds between tries. AfterMaxRetries, the batch stays queued for the next flush. The .NET SDK does not readRetry-After.- On
401or403the SDK stops sending and keeps the batch. Other4xxresponses drop the batch and reportdropped. - The queue is not written to disk. In short-lived processes and serverless
handlers,
await whisperr.ShutdownAsync()orFlushAsync()before you return.
See Delivery, retries, idempotency.
Options
Section titled “Options”| Option | Default | Notes |
|---|---|---|
ApiKey | — | Server key (wrk_…). Required unless Disabled. |
BaseUrl | https://api.whisperr.net | Ingestion base URL. |
AppVersion | — | Your release version. Sent as context.app_version. |
FlushAt | 20 | Send when this many operations are queued. |
FlushInterval | 10 seconds | Background send interval. TimeSpan.Zero turns the timer off. |
MaxQueueSize | 10000 | Oldest operations drop when the queue is full. |
MaxBatchSize | 500 | Events per batch request. 500 is the maximum. |
MaxRetries | 6 | Retries before the batch waits for the next flush. |
RequestTimeout | 10 seconds | Per-request timeout. |
Disabled | false | Captures and sends nothing. Useful in tests. |
Debug | false | Writes warnings to Logger, or to stderr. |
Logger | — | Action<string> used when Debug is on. |
OnError | — | Action<WhisperrError>. error.Code is auth, dropped or retry_exhausted. |
HttpClient | private client | Pass your own for custom handlers or tests. |
Common questions
Which .NET versions does the Whisperr package support?
The Whisperr package targets net8.0 and netstandard2.0, which covers .NET Framework 4.7.2 and later. Whisperr.AspNetCore targets net8.0.
Does the .NET SDK support Native AOT?
Not as a guarantee. The SDK uses reflection-based System.Text.Json. Trimmed deployments are supported; prefer JIT or ReadyToRun for now.
Do I need to flush the client in ASP.NET Core?
No. AddWhisperr registers WhisperrClient as a singleton, and the host disposes it on shutdown, which runs a final flush. Do not await FlushAsync on the request path.
Which API key does the .NET SDK use?
The server key (wrk_…), shown as a Server key in Settings → API keys. Keep it in configuration or an environment variable on your server.