{
  "id": 4108573,
  "title": "How a Timeout Can Charge Your Customer Twice (And How to Stop It in Laravel)",
  "url": "https://urgent.news/2026/08/29/how-a-timeout-can-charge-your-customer-twice-and-how-to-stop-it-in",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-08-29T04:45:30.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/eliasalrgeaidev/how-a-timeout-can-charge-your-customer-twice-and-how-to-stop-it-in-laravel-1f9a"
  },
  "original_language": "en",
  "account": "When a customer attempts to make a payment, the request successfully reaches your server and the transaction is completed. However, if the client's connection drops during the response transmission, they receive a timeout error. Since the client cannot determine if the payment was successful or not, they retry the payment. This leads to a situation where the server receives a second POST request for the same payment, unaware if it was charged before. If the system doesn't handle this scenario correctly, the customer ends up being charged twice.\n\nAn idempotency key, typically a unique identifier like a UUID, is used to represent a specific payment transaction. It is generated by the client and stored along with the request. When a client retries the payment due to a timeout, it uses the same idempotency key instead of creating a new one. The server checks if the transaction has been processed before by verifying the idempotency key.\n\nThe naive approach to handling this scenario involves checking if the idempotency key exists in the database. If it doesn't, the payment is processed and the key is saved in the database. However, this approach has a significant flaw. Since servers can handle multiple requests simultaneously, if two requests arrive at the same time, both could check for the existence of the key before either one saves it. This results in both requests being processed simultaneously, leading to double charges.\n\nThe solution to this issue is atomic locking. Instead of checking for the key's existence and saving it as separate steps, the check and save must happen together as one process. Laravel's Cache::lock() method is used to achieve this. It blocks the request for a certain period of time, allowing the first request to finish processing. Once the first request is done, the result is reused. If another request attempts to process the payment during this time, it is blocked until the first request is complete. This ensures that double charges are impossible.\n\nIt's also important to store the response along with the idempotency key, not just the key itself. This is crucial because if the client retries the payment, they need to receive the exact same response that was supposed to be sent back in the original request. This makes the retry appear as a normal, successful request to the client. Additionally, idempotency keys should not be stored indefinitely. They should have a time-to-live (TTL), often set to 24 hours, to prevent unnecessary storage and ensure old keys are deleted. This can be implemented in Laravel using a scheduled job that scans the database for old keys and deletes them.",
  "summary": "A customer attempts a payment. The request safely reaches your server, and the transaction is performed successfully. Then, while the response is being sent back to the client, the connection drops. The client never receives a success message. Instead, they see a timeout. The client has no possible way of knowing if the charge went through or not, so they logically retry. This is where problems…",
  "key_points": [],
  "editors_take": "Using idempotency keys with atomic locking prevents double charges by ensuring only one payment request is processed, allowing customers to retry payments without incurring extra costs.",
  "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."
}