Idempotent.NET.Http 1.0.0

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

Idempotent.NET - idempotency middleware and stores for .NET

NuGet NuGet downloads .NET 8 .NET 10 License

Idempotent.NET

Idempotent.NET gives ASP.NET Core APIs a Stripe-style idempotency layer: a client sends one Idempotency-Key, the first request executes, and safe retries receive the stored result instead of running the side effect again.

It is built for the messy part of real systems: mobile retries, load balancers, client timeouts, queue redelivery, double-clicks, and services that must not create the same payment, order, booking, or command twice.

Install

dotnet add package Idempotent.NET

Add a durable store when the app runs on more than one node:

dotnet add package Idempotent.NET.Stores.Redis
dotnet add package Idempotent.NET.Stores.Sql
dotnet add package Idempotent.NET.Stores.EntityFrameworkCore

Minimal API

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddIdempotency()
    .AddMemoryStore();

var app = builder.Build();

app.UseIdempotency();

app.MapPost("/orders", (CreateOrder request) =>
        Results.Created($"/orders/{request.Id}", request))
    .RequireIdempotency();

app.Run();

Send an Idempotency-Key header with unsafe requests:

POST /orders HTTP/1.1
Idempotency-Key: 7f36df0d-a8e8-4b9f-a673-cbc90f11b9f4
Content-Type: application/json

{ "sku": "coffee", "quantity": 2 }

The first request claims the key and stores the status code, headers, body, and fingerprint. A retry with the same key and same request fingerprint gets the cached response. A retry with the same key but different request content is rejected as a conflict.

Controllers

[ApiController]
[Route("orders")]
public sealed class OrdersController : ControllerBase
{
    [HttpPost]
    [Idempotent]
    public IActionResult Create(CreateOrder request) =>
        Created($"/orders/{request.Id}", request);
}

Register the middleware once:

builder.Services.AddIdempotency().AddMemoryStore();
app.UseIdempotency();

Stores

Store Package Use it for
Memory Idempotent.NET / Idempotent.NET.Stores.Memory Local development, tests, single-node services
Redis Idempotent.NET.Stores.Redis Low-latency shared state across many API nodes
SQL Idempotent.NET.Stores.Sql PostgreSQL, SQL Server, or SQLite without EF Core
EF Core Idempotent.NET.Stores.EntityFrameworkCore Apps that want idempotency rows inside an existing DbContext

Redis

builder.Services
    .AddIdempotency()
    .AddRedisStore(options =>
    {
        options.Configuration = builder.Configuration.GetConnectionString("Redis")!;
        options.KeyPrefix = "orders-api:";
    });

SQL

Supports PostgreSQL, SQL Server, and SQLite. The store auto-creates the idempotency_keys table on first startup (AutoMigrate = true by default) — no manual DDL or migration script needed.

builder.Services
    .AddIdempotency()
    .AddSqlStore(options =>
    {
        options.Provider = SqlProvider.PostgreSql;   // PostgreSql | SqlServer | Sqlite
        options.ConnectionString = builder.Configuration.GetConnectionString("Postgres")!;
        // options.AutoMigrate = false;  // opt out if you manage schema yourself
        // options.TableName = "idempotency_keys";  // override table name
    });

<details> <summary>Table schema (all providers)</summary>

Column PostgreSQL SQL Server SQLite
storage_key (PK) VARCHAR(64) NVARCHAR(64) TEXT
state INTEGER INTEGER INTEGER
fingerprint_hex VARCHAR(64) NVARCHAR(64) TEXT
created_at_ticks BIGINT BIGINT INTEGER
completed_at_ticks BIGINT BIGINT INTEGER
lease_expires_at_ticks BIGINT BIGINT INTEGER
expires_at_ticks BIGINT BIGINT INTEGER
status_code INTEGER INTEGER INTEGER
headers_json TEXT NVARCHAR(MAX) TEXT
body_base64 TEXT NVARCHAR(MAX) TEXT
content_type TEXT NVARCHAR(MAX) TEXT
body_truncated INTEGER INTEGER INTEGER

Storage keys are SHA-256 hashes — the raw client Idempotency-Key value never appears in the database.

</details>

EF Core

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.AddIdempotencyKeys();
    // optionally: modelBuilder.Entity<IdempotencyKeyEntity>().ToTable("my_keys");
}

How It Works

new key
  -> claim as InFlight
  -> execute handler
  -> store response + fingerprint + TTL
  -> replay later identical requests

duplicate in-flight key
  -> return conflict or wait, based on options

duplicate completed key with a different fingerprint
  -> reject as fingerprint mismatch

Storage keys are SHA-256 hashes of the client key, scope, HTTP method, and path. The raw client key is never interpolated into SQL and is not used directly as a Redis key.

Beyond HTTP

MediatR pipeline behavior

builder.Services.AddIdempotentBehavior();

Implement IIdempotentRequest<TResponse> on any MediatR request to make it idempotent end-to-end, including through message queues and retry loops.

Outbound HTTP key injection

builder.Services.AddHttpClient("payments")
    .AddIdempotencyKeyHandler();

Propagates the current request's idempotency key to outbound HttpClient calls automatically, so downstream services deduplicate on the same key without extra code.

Wolverine inbox pattern

See samples/InboxWithMassTransit/ for an end-to-end inbox example using Wolverine.

Source Generator and Analyzers

The Idempotent.NET.SourceGenerators package (automatically included with Idempotent.NET) ships three Roslyn analyzers and a compile-time registry generator.

Analyzers

Diagnostic Severity Description
IDM001 Warning [Idempotent] applied to a safe HTTP verb (GET, HEAD, OPTIONS). Safe verbs are already idempotent; the attribute has no effect and signals a design mistake.
IDM002 Info [Idempotent(RequireKey = true)] is declared but no 400 response is documented in OpenAPI attributes. Clients need to know a missing key returns 400.
IDM003 Error A type implements IIdempotentRequest<T> but has no IdempotencyKey string property. The MediatR behavior requires this property to compute the storage key.

Registry generator

At compile time the generator discovers every IIdempotentRequest<T> implementation in your assembly and emits:

// auto-generated — do not edit
public static partial class IdempotencyEndpointRegistry
{
    public static readonly FrozenSet<string> RequestTypes = FrozenSet.ToFrozenSet(new[]
    {
        "MyApp.Commands.CreateOrderCommand",
        // ...
    });
}

This lets tooling and middleware inspect registered idempotent request types at zero runtime cost.

vs IdempotentAPI

IdempotentAPI is the established library in this space. Here is a direct comparison:

Feature Idempotent.NET IdempotentAPI v2.6
Minimal API (RequireIdempotency()) ✅ First-class ❌ Controllers only
MediatR pipeline behavior ✅ Built-in ❌ Not supported
Outbound HttpClient key injection ✅ Built-in ❌ Not supported
Wolverine inbox pattern ✅ Sample included ❌ Not supported
Raw SQL store (no ORM) ✅ PostgreSQL, SQL Server, SQLite ❌ Not supported
EF Core store ✅ Plugs into existing DbContext ✅ Supported
Redis store ✅ Supported ✅ Supported
Auto-creates SQL table ✅ Yes (AutoMigrate = true) N/A
Roslyn analyzers ✅ IDM001, IDM002, IDM003 ❌ Not supported
.NET 10 support ❌ (as of v2.6)
Request fingerprinting (conflict detection) ✅ SHA-256
In-flight lease / watchdog renewal

The core difference in scope: IdempotentAPI is an HTTP filter for controller APIs. Idempotent.NET covers the full lifecycle — HTTP layer, message bus, outbound clients, and raw query infrastructure.

Benchmarks

BenchmarkDotNet ShortRun on Windows 11, .NET SDK 10.0.103, runtime .NET 10.0.7, x64 RyuJIT AVX2.

Method Mean Allocated
ComputeStorageKey 436.7 ns 600 B
TryClaim_NewKey 2,563.4 ns 1008 B
TryClaim_AlreadyCompleted 580.4 ns 752 B
TryClaim_AlreadyInFlight 627.5 ns 752 B
GetAsync_Completed 492.1 ns 736 B
GetAsync_Missing 440.1 ns 592 B

Run them locally:

dotnet run -c Release --project benchmarks/Idempotent.NET.Benchmarks

Packages

Package Purpose
Idempotent.NET Main package with ASP.NET Core integration and memory store
Idempotent.NET.Core Abstractions, keys, fingerprints, responses, and store contracts
Idempotent.NET.AspNetCore Middleware, endpoint filters, and [Idempotent]
Idempotent.NET.Stores.Memory In-process store
Idempotent.NET.Stores.Redis Redis-backed distributed store
Idempotent.NET.Stores.Sql Raw ADO.NET store for PostgreSQL, SQL Server, and SQLite
Idempotent.NET.Stores.EntityFrameworkCore EF Core-backed store
Idempotent.NET.Mediator MediatR request pipeline behavior
Idempotent.NET.Http Outbound HttpClient idempotency-key handler
Idempotent.NET.SourceGenerators Roslyn analyzers (IDM001–IDM003) and compile-time request registry

Samples

The samples/ folder includes runnable Minimal API, controller, MediatR, Wolverine, outbound HTTP, and inbox examples.

License

MIT

Product 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. 
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 97 5/3/2026
1.0.0-preview.2 50 5/3/2026
1.0.0-preview.1 58 5/3/2026

Initial public release of Idempotent.NET: ASP.NET Core idempotency middleware, in-memory/Redis/SQL/EF Core stores, MediatR-style pipeline behavior, outbound HttpClient key handling, source generator support, samples, tests, and benchmarks.