Duplicate webhook events are not rare.
They are expected behavior.
Most payment providers retry webhook calls when:
Your endpoint times out
They don’t receive a 200 OK
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.
Why Duplicate Webhooks Happen
Payment gateways (including Worldpay, Stripe, etc.) retry webhooks intentionally.
From their perspective:
Delivery is not guaranteed.
From your perspective:
You might process the same event twice.
If you don’t protect against it, your system may:
- Mark a payment as captured twice
- Send duplicate confirmation emails
- Create duplicate order records
What Is Idempotency?
Idempotency means:
Processing the same event multiple times results in the same final state.
In other words:
The second attempt does nothing.
A Simple Strategy in ASP.NET Core
One reliable approach:
- Store each webhook event ID in the database.
- Before processing, check if it already exists.
- If it exists → ignore.
- If not → process and save.
Example:
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 real production systems, you also want:
- Full status transition tracking
- Previous vs new state comparison
- Raw payload storage
- Source tracking (Webhook / Manual / API)
That’s where structured audit logging becomes critical.
Handling Payment Webhook Retries Safely in ASP.NET Core
If you're integrating with payment providers like Worldpay or any webhook-based gateway, you must assume that webhook retries will happen.
Common search scenarios developers face include:
- How to prevent duplicate webhook processing in ASP.NET Core
- How to implement idempotent webhook handling in .NET
- Handling payment webhook retries safely
- Avoiding duplicate payment status updates
- Preventing double order creation from webhook calls
The key principle is simple:
Every webhook event must be processed in an idempotent way.
In ASP.NET Core, this typically means:
- Persisting a unique event ID
- Checking for existing records before executing business logic
- Storing structured audit logs for traceability
- Returning proper HTTP 200 responses only after successful persistence
Without this protection, payment systems become fragile — especially under network instability or gateway retry policies.
Idempotent webhook processing is not an optimization.
It is a requirement for reliable payment architecture.
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.
I recently extracted a lightweight audit logging component from a real production payment integration that helps handle scenarios like this.
🎁 I’m offering it free for early developers while gathering feedback.
👉 If you're implementing payment gateways like Worldpay in production and want a complete working integration guide (with logging, validation, and real-world architecture), you can access the full implementation here: https://ramapratheeba.gumroad.com/l/gdzkpw
Top comments (0)