HaveIBeenPwned.Client is an unofficial, AOT-compatible .NET client for the complete Have I Been Pwned REST API v3.
The repository publishes three packages:
| Area | Operations | Client | Access |
|---|---|---|---|
| Email search | Direct account search, filtered/truncated responses, six-character k-anonymity search | IPwnedBreachesClient |
Core, Pro, or High RPM; k-anonymity requires Pro or High RPM |
| Domain verification | Generate DNS token, verify DNS token, send verification email | IPwnedDomainClient |
Pro |
| Domain search | Breached domain and subscribed domains | IPwnedDomainClient |
Core or Pro |
| Breaches | All breaches, single breach, latest breach, data classes | IPwnedBreachesClient |
Public |
| Stealer logs | By email, website domain, or email domain | IPwnedStealerLogsClient |
Pro |
| Pastes | Pastes for an email address | IPwnedPastesClient |
Core, Pro, or High RPM |
| Subscription | Current status and capabilities | IPwnedClient |
Authenticated |
| Pwned Passwords | SHA-1 and NTLM range searches with optional padding | IPwnedPasswordsClient |
Public |
IPwnedClient aggregates all specialized clients.
Install the client:
dotnet add package HaveIBeenPwned.Clientbuilder.Services.AddPwnedServices(options =>
{
options.ApiKey = builder.Configuration["HibpOptions:ApiKey"];
options.UserAgent = "my-service/1.0";
});The API key is optional when only public breach-catalogue or Pwned Passwords operations are used:
builder.Services.AddPwnedServices(options =>
{
options.UserAgent = "my-public-service/1.0";
});Resolve either a specialized client or IPwnedClient:
var breaches = serviceProvider.GetRequiredService<IPwnedBreachesClient>();
var allApis = serviceProvider.GetRequiredService<IPwnedClient>();IPwnedClient publicClient = new PwnedClient();
IPwnedClient authenticatedClient = new PwnedClient("<HIBP API key>");var breaches = await client.GetBreachesForAccountAsync(
"user@example.com",
includeUnverified: false,
domain: "example.com",
cancellationToken);var breachHeaders =
await client.GetBreachHeadersForAccountUsingKAnonymityAsync(
"user@example.com",
cancellationToken);The client normalizes and hashes the address locally, sends only the first six SHA-1 characters, matches the returned suffix locally, and does not expose nonmatching range entries.
var token = await client.GenerateDomainVerificationDnsTokenAsync(
"example.com",
cancellationToken);
// Publish token.TxtRecordValue as a DNS TXT record, then:
await client.VerifyDomainViaDnsAsync("example.com", cancellationToken);Email verification is also supported:
await client.SendDomainVerificationEmailAsync(
"example.com",
DomainVerificationEmailAlias.Security,
cancellationToken);var result = await client.GetPwnedPasswordAsync(
plainTextPassword,
addPadding: true,
cancellationToken);
var ntlmResult = await client.GetPwnedPasswordWithNtlmAsync(
plainTextPassword,
addPadding: true,
cancellationToken);Only a five-character hash prefix is sent to the Pwned Passwords service. Padding rows with a zero count are discarded.
Documented 404 Not Found responses map to the operation's no-result shape, such as an empty collection or null. Other HTTP failures throw PwnedApiException:
try
{
await client.GetBreachesForAccountAsync("user@example.com");
}
catch (PwnedApiException exception)
{
Console.WriteLine(exception.StatusCode);
Console.WriteLine(exception.ResponseContent);
Console.WriteLine(exception.RetryAfter);
}Cancellation, transport failures, and JSON failures are never converted into empty successful-looking results.
The client emits dependency-free .NET diagnostics through a stable ActivitySource and Meter. Register them with an existing OpenTelemetry setup:
builder.Services
.AddOpenTelemetry()
.WithTracing(tracing =>
tracing.AddSource(PwnedClientTelemetry.ActivitySourceName))
.WithMetrics(metrics =>
metrics.AddMeter(PwnedClientTelemetry.MeterName));This integrates with standard OpenTelemetry exporters and Aspire service defaults. If an Aspire ServiceDefaults project already configures OpenTelemetry, add the source and meter to that existing configuration.
The following instruments are emitted:
| Instrument | Type | Description |
|---|---|---|
hibp.client.request.count |
Counter | Logical client requests |
hibp.client.request.duration |
Histogram, seconds | Logical request duration |
hibp.client.request.failure.count |
Counter | Failed requests |
hibp.client.rate_limit.count |
Counter | HTTP 429 responses |
Spans and metrics use low-cardinality operation, API surface, method, outcome, and status tags. The client does not attach email addresses, domains, passwords, hashes, API keys, response bodies, or raw URLs to its own telemetry.
Authenticated HIBP endpoints place email addresses or domains in the HTTP path. If standard HttpClient instrumentation is also enabled, configure its filtering or redaction so url.full is not exported for these hosts. The library-level spans remain available even when the lower-level HTTP spans are filtered.
Install HaveIBeenPwned.Client.PollyExtensions to configure the standard .NET HTTP resilience pipeline. Its default policy:
- honors the HIBP
Retry-Afterheader; - retries HTTP 429 and 503 responses;
- applies jitter, a circuit breaker, and an overall timeout;
- redacts the
hibp-api-keyheader from HTTP logs.
Custom HttpStandardResilienceOptions can be supplied through the package's AddPwnedServices overloads.
| Key | Type | Default |
|---|---|---|
HibpOptions__ApiKey |
string? |
null |
HibpOptions__UserAgent |
string |
.NET HIBP Client/{AssemblyFileVersion} |
HibpOptions__SubscriptionLevel |
HibpSubscriptionLevel? |
null |
Every API request includes a user agent. Authenticated email, domain, paste, stealer-log, and subscription operations require a valid HIBP API key.
- Do not run password searches for every keystroke; wait until password entry is complete.
- Do not log or export plaintext passwords or
PwnedPassword.PlainTextPassword. - K-anonymity range results that do not match the requested account or password must not be retained.
- Breach and paste API data is licensed under CC BY 4.0; consuming experiences must attribute Have I Been Pwned.
- Pwned Passwords does not require attribution.
