
Many developers spend most of their time designing an API around good endpoints, payloads in requests, status codes for responses, and how to model data. All these aspects are important; however, without proper consideration for the way things really work in real-world systems, they are insufficient. Real-world systems experience issues with users accidentally clicking a button, networks failing, payment processors resending webhooks for retries, queues re-delivering messages, and even browsers sending requests again after a timeout. The issue here is that the same action could be performed more than once, resulting in the creation of duplicated orders, duplicated payments, duplicated email notifications, etc., which would also lead to inconsistencies in the record. At this point, idempotency comes into play. An API is idempotent if calling it the same number of times will have exactly the same effect as calling it once. Idempotence means that if a user submits the same request twice, the system will know the first time was a valid submission and will either return the same result from the first call or will simply disregard the second identical call. It may seem like idempotence is easy to understand; however, it is another backend design concept that does not become clear until actual failure occurs. Why Idempotency Matters Imagine a checkout page where the user clicks “Place Order.” The frontend sends a request to create the order and charge the card. The backend successfully charges the card, but before the response reaches the browser, the network fails. From the user’s point of view, nothing happened. The browser may retry the request, or the user may click the button again. Without idempotency, the backend may create a second order and charge the user twice. Technically, the API worked exactly as requested, but from a product and business perspective, it failed badly. The same issue appears in many systems. A food delivery app may place two orders. A ticketing platform may reserve two seats. A loan application system may create duplicate applications. A notification service may send the same email repeatedly. A webhook consumer may process the same payment event multiple times. In distributed systems, duplicate requests are not rare edge cases. They are normal production behavior. The Idempotency Key Pattern The most common method for designing idempotent APIs is through the use of an idempotency key. An idempotency key is generated on the client side for each operation. It is then sent with the request (usually as a header). The backend maintains a mapping of keys to the status of requests and responses. When the backend receives the same key again, it determines if it has already completed the operation associated with the key. If the backend has already completed the operation, it responds with the original response; otherwise, it may respond with a conflict or wait before telling the client to retry later. Idempotency-Key: 8f7c2a9d-9c4b-4a9f-b321-91d7a1c2e222 If the key is new, the backend will process the request and store the results. This allows for retries to be considered safe since the retry is no longer treated as a new business action. Where Idempotency Should Be Used There isn't always a requirement for all APIs to have an idempotency key. A few examples of this are when you read a user's profile; this is automatically safe as there are no side effects created by this action. When a user wants to update one of their notification preferences to "enabled," this action will produce the same result regardless of how many times they attempt it. Idempotency really becomes necessary with those actions in APIs that cause side effects. Examples of such actions include ordering products, debiting money through payment, booking appointments, reserving product stock, submitting application forms, sending emails, processing webhooks, running background jobs on your server, or performing account transfers. A useful rule is this: if repeating the request can create duplicate business impact, the API should be idempotent. Idempotency Is Not Just a Frontend Problem Many teams will attempt to prevent duplicate actions in their application on the client side by disabling the submit button once clicked. While this helps, it is insufficient. Client-side solutions can assist with reducing the number of duplicated transactions due to user errors; however, they cannot prevent the issues that arise when attempting to retry a transaction, such as browser refreshes, network failures on mobile devices, queue redeliveries, or webhook replays. Idempotence needs to be enforced at the backend since it represents the truth. Improving the user experience for the client layer provides value, but ensuring correctness exists at the backend. A simple example would be a checkout button being disabled after one click; however, the payment API should always use an idempotency key. Both layers provide some level of protection against duplication of a request. However, only the backend guarantees protection against duplication of requests in the event of failure. Storing Idempotency Records An idempotency table can simply have an idempotency key, a user ID, a request hash, a status, a response body, a creation timestamp, and an expiration timestamp. A request hash is essential here since this ensures that a key cannot be used twice with differing payloads. If a client uses the same key but uses it again after changing some elements like order quantity or product list, the server has to decline it; otherwise, the second call will be treated as another valid retry. The status field in the table is also very valuable. For example, there can be requests in processing mode, successful and unsuccessful. Therefore, if a duplicate request comes in during the time the original was still being processed, then this will help determine what action to take. Finally, an idempotency record does not have to exist forever. Records can exist anywhere from a couple of hours to weeks depending on the system. Systems dealing with payments and finances will typically keep these records around longer, as duplicate events can cause much more serious side effects. Common Mistakes One of the most common mistakes in using idempotency keys is to generate the idempotency key on the server. This will not work for retries, as each time you try again, there could be a different key returned from the server. Typically, an idempotency key is best created on the client when it begins to execute the user's operation. Another mistake is storing only the key but not the response. If the original request succeeded but the client never received the response, the retry should return the original result. Otherwise, the client remains uncertain. Finally, another problem that occurs is treating all failed requests equally. For example, if a request fails during validation prior to any side effect occurring, then the client can probably just correct their request and resubmit. However, if the failure occurred after a payment was made or inventory had been reserved, then the backend needs to make sure it does not repeat the side effects. Conclusion The use of idempotence as a practical pattern to create reliable APIs is among the most useful concepts in developing a system that can handle unexpected behavior from users or service providers. The fact is that many production environments are based on an assumption that every request will have at least some level of reliability, and therefore a response will always be received by the client. However, there are many reasons why this may not be true: requests may be duplicated due to network errors; responses may be delayed or lost due to network problems; and an external system may attempt to resend a message if its previous send was unsuccessful. Rather than assuming these types of failures will never occur, idempotent designs allow you to safely repeat operations on the backend. While idempotency is a technically beneficial advancement for creating reliable systems, it also provides numerous benefits for both business and end-users. Specifically, idempotent API designs protect users against duplicative charges. Additionally, they protect businesses from unnecessary support calls. Finally, idempotent designs protect systems from being placed into a potentially inconsistent state. Therefore, all APIs that produce some type of meaningful side effect should be built using idempotent techniques from day one rather than attempting to add them as needed when the first "duplicate" transaction occurs.
View original source — Hacker Noon ↗


