{
  "id": 12576661,
  "title": "How to Stop Double Charges: Idempotency Keys in ASP.NET Core Payment APIs",
  "url": "https://urgent.news/2026/10/07/how-to-stop-double-charges-idempotency-keys-in-asp-net-core-payment",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-10-07T07:41:46.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/ali_elyas/how-to-stop-double-charges-idempotency-keys-in-aspnet-core-payment-apis-504m"
  },
  "original_language": "en",
  "account": "Double charges can occur when a customer taps Pay and the mobile connection stalls, causing the app to retry the request. This is not an uncommon issue, as any API that modifies money, inventory, or orders may receive duplicate requests from retries, double-clicks, and proxy timeouts. The solution is to use an idempotency key, which is a unique identifier sent by the client in the Idempotency-Key header with every write operation. The server ensures that the same key produces the same result, regardless of how many times it arrives.\n\nTo implement idempotency keys in an ASP.NET Core payment API, create a class to store the key, request hash, response body, and status code. Define a unique constraint in the database on the ClientId and Key fields, which handles locking for simultaneous requests. In the endpoint logic, retrieve the key from the request headers, generate a hash of the request body, and attempt to add a new IdempotencyRecord to the database. If a record already exists with the same key and body, return the stored response. If the key exists but the body is different, return an HTTP 422 error. If the previous request is still running, return HTTP 409.\n\nHandle pitfalls such as crashes between insert and update by scheduling a background job to periodically re-check records older than a few minutes against the payment provider's idempotency support. Set an expiry time for the records, typically 24 hours to a few days, to prevent unbounded growth. Avoid hashing volatile fields, such as timestamps, as this may result in a 422 error for legitimate retries. Additionally, consider storing validation errors to ensure correct behavior when replaying errors.\n\nTesting the implementation involves firing 20 parallel requests with the same key using a small script or load tool. Exactly one charge should be created, and all 20 responses should be identical (or 409 while in flight). If two charges are generated, the key is not part of a uniqueness constraint. Implementing idempotency keys on day one is inexpensive and prevents painful retrofits after double-charge incidents. It creates a predictable contract for payment-adjacent .NET projects, as my team at QllmSoft does on every project.",
  "summary": "A customer taps \"Pay\", the mobile connection stalls, and the app retries. Your server processed the first request, so now there are two charges. This is not a rare edge case. Any API that mutates money, inventory or orders will see duplicate requests from retries, double-clicks and proxy timeouts. The standard fix is an idempotency key: the client sends a unique Idempotency-Key header with every…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}