Idempotency
1. The Problem
Imagine an ASP.NET Core Document Service that receives a webhook from an external e-signature provider when a document is signed.
E-Signature Provider
|
| DocumentSigned
v
ASP.NET Core
Document Service
The Document Service records the signed event and sends a notification to people who have access to the document.
Everything works normally.
But the provider doesn't receive the response from your API quickly enough. So it retries the webhook.
Attempt 1 ---> Document Service
|
v
Processed
|
X response lost
Attempt 2 ---> Document Service
|
v
Processed AGAIN
Now the document history contains two “signed” entries. The users may receive the same notification twice.
The provider did nothing unusual. Retries are normal in distributed systems.
Your application needs to make those retries safe.
2. What's Actually Going Wrong?
The Document Service treats every incoming webhook as a new event. It doesn't know that:
Attempt 1 Attempt 2
are actually attempts to deliver the same logical event.
This can happen with:
- HTTP client retries
- Webhook retries
- Network failures
- Message redelivery
- Background job retries
- Service-to-service communication
The problem becomes serious when processing the request has side effects:
DocumentSigned +-- Add history entry +-- Send notification +-- Update document status +-- Trigger another process
If the same event is processed twice, all of those operations may happen twice.
3. The Concept
Idempotency means that processing the same logical operation multiple times produces the same intended business result as processing it once.
For HTTP APIs and webhooks, a common approach is to use an Idempotency Key or another unique event identifier.
For example, the e-signature provider sends:
Event ID: EVT-78291 DocumentSigned
The Document Service records that event ID. When the same event arrives again:
DocumentSigned
Event ID: EVT-78291
|
v
Document Service
|
v
Already processed?
|
+-- YES --> Don't process again
|
+-- NO ---> Process
The second delivery is safely ignored.
The key idea is:
The same logical operation can be delivered more than once, but its business effect should happen only once.
4. How Do You Fix It?
For an ASP.NET Core webhook, the conceptual flow is:
E-Signature Provider
|
| Event ID: EVT-78291
v
ASP.NET Core Document Service
|
v
Idempotency Check
|
+----+-----+
| |
New Already seen
| |
v v
Process Return safely
|
v
Record event as processed
The idempotency information needs to be stored somewhere that can be shared across application instances.
Document Service
|
v
Idempotency Store
|
+-- Azure SQL
|
+-- Azure Managed Redis
For important business operations, a durable store such as Azure SQL Database is often a better fit when the idempotency record needs strong durability and transactional coordination with business data.
The exact storage choice depends on the required guarantees.
5. The Important Part: Concurrency
A simple check can still fail.
Imagine two identical webhook deliveries arrive almost simultaneously:
Webhook A --> "Is EVT-78291 already processed?"
|
v
NO
Webhook B --> "Is EVT-78291 already processed?"
|
v
NO
Both requests may now process the event.
Webhook A --> Process Webhook B --> Process
So the idempotency check itself must be atomic.
For example, a database can use a unique constraint/index on the event identifier so that only one request can successfully claim it.
Webhook A --+
|
v
Azure SQL
^
|
Webhook B --+
One unique event ID
|
v
Only one succeeds
This is a critical part of real-world idempotency.
6. Idempotency Is Not Just “Don't Insert Twice”
Consider this flow:
Webhook | v Check event ID | v Not processed | v Add signed history | v Send notification | X Application crashes
Now the system has to reason about what happened.
Did the event get processed? Was the notification sent? Should a retry send the notification again?
This is why idempotency is ultimately about protecting the business effect, not simply storing a request ID.
For database operations, the idempotency record and business change may need to participate in the same transaction where appropriate.
For workflows involving a database and messaging system, patterns such as the Outbox Pattern may also be relevant.
7. Idempotency and Azure Service Bus
The same principle applies when consuming messages from Azure Service Bus.
A message can be delivered again if processing isn't successfully completed.
Azure Service Bus
|
v
Document Processor
|
v
Process message
|
X
processing/settlement problem
|
v
Message delivered again
Your consumer should therefore be designed to handle duplicate delivery safely.
Message
|
v
Already processed?
|
+-- YES --> Skip safely
|
+-- NO ---> Process
|
v
Record completion
This is commonly called an idempotent consumer.
The important mindset is:
Don't assume the message will arrive exactly once. Make processing safe if it arrives more than once.
8. Idempotency vs Exactly-Once
These concepts are related but not identical.
You should not design a distributed system around the assumption that every operation will physically execute exactly once. Instead:
Operation may be delivered
or attempted more than once
|
v
Detect duplicate
|
v
Business effect happens
only once
Idempotency gives you a way to achieve the desired business outcome even when the underlying infrastructure may retry or redeliver.
9. When Should You Use It?
Think about idempotency whenever an operation has an important side effect. Examples:
- Webhooks
- Creating resources
- Processing payments
- Updating balances
- Sending important notifications
- Creating shipments
- Processing Azure Service Bus messages
- Retrying external API calls
- Background jobs
Ask yourself:
If this operation runs twice, can the business state become incorrect?
If yes, idempotency should be considered.
10. What Happens If You Don't Handle It?
The dangerous thing about duplicate processing is that the application may not throw an exception. Everything can appear to work.
Webhook retry
|
v
Application processes it
|
+-- Duplicate history
+-- Duplicate notification
+-- Duplicate record
+-- Duplicate business action
From the application's perspective:
Request 1 -> Success Request 2 -> Success
But from the business perspective:
One event -> Two business effects
That's the real problem.
11. Visualize It in Everyday Terms
Imagine a hotel reservation desk. You call and ask:
“Reserve room 205.”
The receptionist creates the reservation. But the phone connection drops before you receive the confirmation. You call again.
Without idempotency:
Call 1 | v Reserve Room 205 Call 2 | v Reserve Room 205 AGAIN
With a reservation reference:
Reservation Reference: ABC123
|
v
Receptionist
|
v
Already handled?
/ \
Yes No
| |
v v
Return existing Create
reservation reservation
The mapping is:
- Reservation reference → Idempotency Key / Event ID
- Reservation → Business operation
- Receptionist records → Idempotency Store
- Calling again → Retry
- Same reference → Same logical operation
- Existing reservation → Previous result
The key idea:
The request may happen again. The business effect should not happen again.
12. What Should You Remember?
Remember these five things:
1. Retries are normal.
Networks fail. Clients retry. Webhooks retry. Messages can be redelivered.
2. Duplicate delivery doesn't necessarily mean duplicate intent.
Attempt 1
Attempt 2
Attempt 3
|
v
One logical operation
3. Give the operation an identity.
Use an Idempotency Key, webhook event ID, message ID, or another reliable identifier.
4. Make the check atomic.
Two identical requests can arrive at the same time.
5. Protect the business effect.
The goal isn't simply to avoid processing the HTTP request twice.
Don't accidentally perform the business operation twice.
13. .NET + Azure Context
For a typical ASP.NET Core + Azure system:
ASP.NET Core Document Service
|
v
Webhook / HTTP / Message
|
v
Idempotency Check
|
v
Azure SQL
- Event ID
- Status
- Result
|
v
Business Operation
Relevant technologies and building blocks include:
- ASP.NET Core Web API
- EF Core
- Azure SQL Database
- Azure Managed Redis, where appropriate
- Azure Service Bus
- Background workers / hosted services
- Outbox Pattern for reliable database-to-message workflows
For a .NET engineer, the mental model should be:
Assume requests, webhooks, and messages can be retried or delivered more than once. Give each logical operation a reliable identity and make the business effect safe against duplicate processing.