How to Handle Duplicate Webhook Events in ASP.NET Core (Idempotency Guide)
Source: Dev.to
Why Duplicate Webhooks Happen
Payment gateways (e.g., Worldpay, Stripe) intentionally retry webhook calls when:
- Your endpoint times out
- They don’t receive a 200 OK response
- Network issues occur
If your system isn’t idempotent, duplicate events can:
- Overwrite valid payment states
- Trigger duplicate business logic
- Cause inconsistent data
- Create financial reconciliation issues
In payment systems, this is dangerous.
What Is Idempotency?
Idempotency means that processing the same event multiple times results in the same final state. In other words, the outcome is unchanged no matter how many times the event is handled.
A Simple Strategy in ASP.NET Core
One reliable approach is to store each webhook event ID in the database and check for its existence before processing.
- Check if the event ID already exists.
- If it exists → ignore the webhook.
- If it does not exist → process the webhook and save the ID.
public async Task HandleWebhook(WebhookEvent webhook)
{
var exists = await _context.PaymentEvents
.AnyAsync(e => e.EventId == webhook.EventId);
if (exists)
return;
var paymentEvent = new PaymentEvent
{
EventId = webhook.EventId,
PaymentId = webhook.PaymentId,
Status = webhook.Status,
CreatedAt = DateTime.UtcNow
};
_context.PaymentEvents.Add(paymentEvent);
await _context.SaveChangesAsync();
// Continue business logic safely
}
This ensures duplicate retries do not corrupt state.
Going Beyond Basic Logging
In production systems you may also want to capture:
- Full status‑transition tracking
- Comparison of previous vs. new state
- Raw payload storage
- Source tracking (Webhook / Manual / API)
Structured audit logging becomes critical for these requirements.
Final Thoughts
Duplicate webhooks are not a bug; they are a guarantee.
If your ASP.NET Core payment integration does not implement idempotency, it is fragile by design.
A lightweight audit‑logging component that helps handle these scenarios is available for early developers: