When HTTP Retries Become Dangerous: Idempotency in Symfony Without the Fairy Tales
Retries are one of those things that look harmless until the first time they duplicate a real business operation. A request times out, so the client retries it. Reasonable. But what if the first request actually reached the server? What if the application already created the order, reserved the stock, sent the message, or called a payment provider — and only the response was lost? From the client's point of view, the request failed. From the application's point of view, it may already be finished. Send the same request again and you can get the worst kind of bug: one that is technically understandable, difficult to reproduce, and very expensive in production. This is the problem that pushed me to build HttpIdempotencyBundle , a small Symfony bundle for explicit HTTP request idempotency. But the interesting part is not the bundle itself. The interesting part is everything that has to be true before we can safely say: "This request is a retry of the same operation, so we should not execute it again." And just as importantly, what we cannot guarantee. A timeout does not mean the operation failed Consider a simple endpoint: #[Route('/orders', methods: ['POST'])] public function createOrder (): JsonResponse { $order = $this -> orderService -> create (); return new JsonResponse ([ 'id' => $order -> getId (), ], 201 ); } Now imagine this sequence: Client -> POST /orders Server -> creates order #742 Server -> sends 201 response Network -> connection dies Client -> sees timeout Client -> retries POST /orders Nothing unusual happened. The client did exactly what clients often do after a timeout. The server did exactly what it was asked to do. And yet, unless we have another mechanism in place, we may now create order #743 as well. The key idea is simple: transport failure and business-operation failure are not the same thing. HTTP cannot always tell the client whether the operation happened. Give the operation an identity A common solution is an Idempotency-Key . The client g