Uma pessoa clica em “pagar”, a rede oscila e o cliente não recebe a resposta. O que ele faz? Tenta de novo. Um worker perde a conexão depois de enviar um POST? Tenta de novo. Um proxy entende que houve falha transitória? Você já sabe. Repetir requisições é comportamento normal de sistemas distribuídos, e é por isso que endpoints que criam efeitos precisam pensar em idempotência antes de serem chamados de confiáveis.
Uma chave de idempotência dá à API um identificador para reconhecer que duas tentativas representam a mesma intenção do cliente. Em vez de criar dois pedidos, duas cobranças ou dois envios de e-mail, o servidor associa a segunda chamada ao resultado que já começou ou já terminou de processar.
O problema não é só POST
HTTP chama PUT e DELETE de idempotentes porque a repetição pretendida leva ao mesmo estado do recurso. Já um POST /payments geralmente representa uma nova ação. O ponto crítico é que o cliente pode não saber se a primeira ação foi concluída: a resposta pode se perder depois que o servidor já gravou no banco.
POST /orders HTTP/1.1
Content-Type: application/json
Idempotency-Key: "b902b6f0-7474-4e18-bced-5ad0c2d0d4d1"
{ "cartId": "cart_42", "paymentMethod": "card" }
Na primeira chamada, o backend registra a chave junto do escopo da operação, um fingerprint do payload e o resultado ou estado de processamento. Se a mesma chave reaparecer com o mesmo conteúdo, ele devolve a resposta armazenada ou informa que a operação ainda está em andamento. Se reaparecer com um payload diferente, é erro de contrato: reutilizar a chave para outra intenção é pedir para o sistema adivinhar.
O fluxo precisa ser atômico
O anti-padrão clássico é consultar se a chave existe, executar a operação e só depois gravar a chave. Duas requisições concorrentes conseguem atravessar essa janela e ambas fazem o trabalho. A proteção deve usar uma restrição única no armazenamento ou uma operação atômica de “criar se não existir”, preferencialmente na mesma transação que registra o efeito de negócio quando isso for possível.
begin transaction;
insert into idempotency_keys (scope, key, request_hash, status)
values ('create-order:user_7', :key, :hash, 'processing');
-- Se a chave já existir, não crie outro pedido.
insert into orders (customer_id, total) values (:customerId, :total);
update idempotency_keys
set status = 'completed', response_body = :response
where scope = 'create-order:user_7' and key = :key;
commit;
O desenho exato muda conforme o banco e o tipo de efeito. Para uma chamada a um provedor externo, pode ser necessário combinar esse registro com uma outbox, fila ou identificador idempotente também do lado do provedor. A chave no seu endpoint não desfaz uma integração que cobra duas vezes se você não propagar a mesma disciplina para fora.
Escopo e prazo importam
Uma chave global sem contexto pode colidir entre usuários ou endpoints. Faça o escopo incluir a operação e, quando fizer sentido, o cliente autenticado ou o tenant. Também defina uma política de expiração de acordo com o risco: reter para sempre pode ser caro; apagar cedo demais devolve a duplicação justamente quando um cliente volta a tentar depois de uma falha longa.
O grupo HTTPAPI da IETF mantém um Internet-Draft para o cabeçalho Idempotency-Key. Ainda é um draft, portanto não é uma regra final da plataforma. Mesmo assim, a proposta é uma boa referência de vocabulário e comportamento para APIs que já precisam resolver o problema agora. O mais importante é documentar: quais endpoints aceitam a chave, por quanto tempo ela vale e o que acontece quando o payload não coincide.
