POST requests playing among us? Try Idempotency!
Making sure your API requests don't deduct customer's money twice!
Sourav Das
12/10/20234 min read


John, frustrated with low network connectivity, hammers that “add to cart” button in your app for 100 times before someone else gets their hands on the item. Congrats to him he has now added 100 pieces of the same item to his cart. Once he gets back his network and finds out what happened, he uninstalls your app and leaves a pretty colorful review in the app store. Guess what, we have got similar reviews pouring in from Karen, Karen and 100 others.
Nightmare fuel for an engineer!
So what happened here?
Well atleast in John’s case there were 99 imposter POST requests among us!
How do we fix this?
Idempotency to the rescue and thankfully this time it's not that scary linear algebra problem.
Idempotency in APIs is a concept where repeated invocations of a particular operation, using the same set of input parameters, consistently yield the same result.
In John’s case to prevent repeated addition of the same item to the cart, we could have disabled the “add to cart” button till we get a success/failure response from the backend or maybe have a loader while the request is being processed.
To save John from some more frustration it's a good practice to have automatic retries in the background till the client receives a response.
But wait, wasn’t sending repeated requests to the backend something John was doing manually?
Yes, but this time we have got things in our control! We send the POST/PUT requests with an identifier for the server to know if a request is a retry.
(For our purposes we call a request a retry when it's the nth attempt to POST the identical resource, initiated without receiving a response from the server for the preceding requests.)
For this whole setup to work, we need some work in both the frontend and backend. In this article I primarily focus on the backend. (because I am a “backend engineer”/”person with CSS phobia”).
For the backend to identify if a request is a retry it should contain something called an idempotency key in its headers. It's a string created by the frontend, generally an UUID, which is unique for every fresh request for the endpoint and same for every retry. In simple terms, the frontend is expected to generate an idempotency key for the first request and use the same key for the subsequent auto retries that are done till it gets the response back from the server.
Now we know the backend is provided with an identifier, let's see how we process it.
What do we need?
A middleware: We are going to have many write requests and it doesn’t make sense to have the same code to check if the request is a retry in every endpoint, instead we will use a middleware which would intercept every incoming request and check if the request is a retry.
The middleware would extract the idempotency key from the header and check if the key already exists in a DB or any storage.
If it does exist we know the request has been already made and has been processed successfully.
Now we can either send out a response to the frontend saying the request has already been made or if the controller for the endpoint has already stored some response corresponding to the idempotency key in the DB, we can just format the data and send it back to the frontend.


The controller: If the middleware decides to forward the request to the controller. The controller does all the required processing and saves the data. If everything goes well it takes the idempotency key from the header and uses it as a key to store any sort of status/data related to the resource corresponding to the idempotency key. This will allow the middleware to send some data back to the frontend when it intercepts a retry request.
The number of idempotency keys can quickly increase therefore, its a good practice to assign an expiry/time to live for the keys.
Choice of DB/storage: Some of the things to consider are:
Latency for fetching the keys: We are intercepting all the write requests and doing a check if they are a retry. Fetching the idempotency keys should not take much time. Perhaps, a type of in-memory storage would be more apt.
Resource efficiency: The volume of idempotency keys is going to quickly increase. As, we will not require the keys to persist forever, a DB which supports assigning expiry/time to live for its records is preferred.
Data Structure and querying: We are generally using a key to either point to a resource stored by the controller or just using it to know if the request has already been processed successfully. Therefore, a simple key value store seems to be the most apt solution.
Considering all these requirements Redis does come to my mind. But please do suggest if you can think of an alternative.
Lets hop on to the frontend!
The frontend usually just needs to generate an idempotency key and attach it to the header of a fresh request. If it's a retry it attaches the same key to the header of the request.
A lot of complexity depends on what you call a retry, is it a user pressing on a button multiple times or the frontend running automatic retries? A common scenario is, the button/component which triggers the request is disabled till we get a success/failure response from the frontend while we keep running retry requests in exponential backoff fashion. Of course, disabling and retrying should be done for a specific period of time and what we do next depends on the business use case.
If you are facing a more complex scenario feel free to mail me, let's discuss!
Here is a complete overview of the architecture:


And of course at the end there is code too. I have written a sample NodeJS code to integrate all these components, feel free to ask any question you have or suggest any changes to code.