When developing web applications, securing requests between the frontend and backend is one of the key tasks. Ignoring this aspect can lead to serious consequences: data leaks, duplicated operations, incorrect financial transactions, and even service outages. In this article, we will examine the main methods of securing requests and take a detailed look at such an important mechanism as idempotency and how to implement an idempotency key in web applications. Before diving into idempotency, let's review the basic methods of securing requests: JWT Tokens — Used to authenticate users and verify the authenticity of a request based on a signature. HTTPS — Protects data from interception by encrypting traffic between the client and server. CSRF Tokens — Protect against cross-site request forgery attacks. Data Validation — Prevents attacks by validating incoming data for correctness. Rate Limiting — Limits the number of requests from a single source per unit of time, protecting against DDoS attacks. CORS Policies — Allow controlling which domains are permitted to send requests to the server. Password Hashing — Ensures the security of stored passwords. HttpOnly/Secure Cookies — Protect session data from theft through XSS attacks. Data Sanitization — Protects the server from attacks involving malicious code execution. Request Signing — Ensures data integrity during transmission. Two-Factor Authentication (2FA) — Adds an additional level of security for critical operations. JWT Tokens — Used to authenticate users and verify the authenticity of a request based on a signature. JWT Tokens HTTPS — Protects data from interception by encrypting traffic between the client and server. HTTPS CSRF Tokens — Protect against cross-site request forgery attacks. CSRF Tokens Data Validation — Prevents attacks by validating incoming data for correctness. Data Validation Rate Limiting — Limits the number of requests from a single source per unit of time, protecting against DDoS attacks. Rate Limiting CORS Policies — Allow controlling which domains are permitted to send requests to the server. CORS Policies Password Hashing — Ensures the security of stored passwords. Password Hashing HttpOnly/Secure Cookies — Protect session data from theft through XSS attacks. HttpOnly/Secure Cookies Data Sanitization — Protects the server from attacks involving malicious code execution. Data Sanitization Request Signing — Ensures data integrity during transmission. Request Signing Two-Factor Authentication (2FA) — Adds an additional level of security for critical operations. Two-Factor Authentication (2FA) These measures create a baseline level of security, but they do not protect against request duplication. For that, idempotency is used. idempotency Let's take a closer look at what that means. What is Idempotency? Idempotency is the property of an operation where performing it multiple times produces the same result as performing it once. Example: Example: If a client sends two identical requests to withdraw funds, the funds should only be withdrawn once. If a client accidentally creates two identical orders, the server should create only one order. If a client sends two identical requests to withdraw funds, the funds should only be withdrawn once. If a client sends two identical requests to withdraw funds, the funds should only be withdrawn once. If a client accidentally creates two identical orders, the server should create only one order. If a client accidentally creates two identical orders, the server should create only one order. To implement idempotency, the client must send a special header Idempotency-Key — a unique operation identifier — with the request. The server checks this key and decides whether to process the request or not. Idempotency-Key To implement idempotency, the following principles must be followed: The client must generate the idempotency key before sending the request. The server stores the key value and the result of request processing. If a request with the same key has already been processed, the server returns the stored result without reprocessing. The client must generate the idempotency key before sending the request. The client must generate the idempotency key The server stores the key value and the result of request processing. The server stores the key value and the result of request processing. If a request with the same key has already been processed, the server returns the stored result without reprocessing. let’s go deeper! Generating an Idempotency Key on the Client The idempotency key should be generated on the client side because only the client knows which requests might be duplicates. The key should meet the following requirements: Be unique for each request. Remain consistent when the same request is retried (e.g., after a network failure). Should not depend on random factors (e.g., time). Be unique for each request. unique Remain consistent when the same request is retried (e.g., after a network failure). consistent Should not depend on random factors (e.g., time). Methods for Generating a Key: Methods for Generating a Key: UUID (v4) This is the most common method. A UUID (Universally Unique Identifier) is a random 128-bit string. UUID (v4) This is the most common method. A UUID (Universally Unique Identifier) is a random 128-bit string. UUID (v4) const idempotencyKey = crypto.randomUUID(); const idempotencyKey = crypto.randomUUID(); Hash of Request Data If requests contain identical parameters, you can generate a key as a hash of the request data. This makes the key deterministic. Hash of Request Data If requests contain identical parameters, you can generate a key as a hash of the request data. This makes the key deterministic. Hash of Request Data const data = JSON.stringify({ userId: 123, amount: 500 }); const idempotencyKey = crypto.createHash('sha256').update(data).digest('hex');. const data = JSON.stringify({ userId: 123, amount: 500 }); const idempotencyKey = crypto.createHash('sha256').update(data).digest('hex');. Combination of Timestamp and Random DataA key can be created from a timestamp (to prevent collisions) and a random number. Combination of Timestamp and Random DataA key can be created from a timestamp (to prevent collisions) and a random number. Combination of Timestamp and Random Data const idempotencyKey = `${Date.now()}-${Math.random().toString(36).substring(2)}`; const idempotencyKey = `${Date.now()}-${Math.random().toString(36).substring(2)}`; Processing the Key on the Server After the client sends a request with the Idempotency-Key header, the server performs the following actions: Idempotency-Key Extracts the key from the header: Extracts the key from the header: key := r.Header.Get("Idempotency-Key") if key == "" { http.Error(w, "Idempotency-Key required", http.StatusBadRequest) return } key := r.Header.Get("Idempotency-Key") if key == "" { http.Error(w, "Idempotency-Key required", http.StatusBadRequest) return } Checks whether such a key already exists in the storage: Checks whether such a key already exists in the storage: If the key exists — returns the stored result. If the key does not exist — processes the request. If the key exists — returns the stored result. If the key exists — returns the stored result. If the key does not exist — processes the request. If the key does not exist — processes the request. If processing is successful: If processing is successful: Stores the result and key in the cache or database. Sets TTL (time-to-live) to automatically delete the key after a defined period. Stores the result and key in the cache or database. Sets TTL (time-to-live) to automatically delete the key after a defined period. Example Implementation in Go var idempotencyCache = sync.Map{} var cacheTTL = 24 * time.Hour func WithIdempotency(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { key := r.Header.Get("Idempotency-Key") if key == "" { http.Error(w, "Idempotency-Key required", http.StatusBadRequest) return } if cached, ok := idempotencyCache.Load(key); ok { w.WriteHeader(http.StatusOK) w.Write(cached.([]byte)) return } // your logic if recorder.statusCode >= 200 && recorder.statusCode < 300 { idempotencyCache.Store(key, recorder.body) time.AfterFunc(cacheTTL, func() { idempotencyCache.Delete(key) }) } }) } var idempotencyCache = sync.Map{} var cacheTTL = 24 * time.Hour func WithIdempotency(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { key := r.Header.Get("Idempotency-Key") if key == "" { http.Error(w, "Idempotency-Key required", http.StatusBadRequest) return } if cached, ok := idempotencyCache.Load(key); ok { w.WriteHeader(http.StatusOK) w.Write(cached.([]byte)) return } // your logic if recorder.statusCode >= 200 && recorder.statusCode < 300 { idempotencyCache.Store(key, recorder.body) time.AfterFunc(cacheTTL, func() { idempotencyCache.Delete(key) }) } }) } Conclusion Idempotency allows you to avoid request duplication and make the system reliable in the event of network failures and repeated calls. Implementing it via middleware in Go simplifies request processing and increases system resilience. However, you need to account for possible mistakes during implementation: TTL too short — The key may disappear before request processing is complete. Non-unique keys — If the key is reused across different operations, the server may return the wrong cached result. Caching errors — Do not cache responses with HTTP 500 or similar errors. TTL too short — The key may disappear before request processing is complete. TTL too short Non-unique keys — If the key is reused across different operations, the server may return the wrong cached result. Non-unique keys Caching errors — Do not cache responses with HTTP 500 or similar errors. Caching errors Idempotency is a simple yet powerful tool for preventing duplicate operations. Start using it in all unsafe HTTP methods (POST, PUT, PATCH) — and you will make your API more stable and predictable. Take care!