Idempotência em APIs REST: o que é e como implementar

Idempotência em APIs

Um cliente clica em “pagar”, a requisição demora, ele clica de novo — e a cobrança sai duplicada. Esse é o problema que a idempotência resolve. Uma operação idempotente pode ser executada uma ou dez vezes com o mesmo resultado final, o que torna seguro repetir requisições que falharam no meio do caminho. Este guia explica quais métodos HTTP já são idempotentes, como implementar Idempotency-Key com Redis, e por que sistemas de pagamento como o Pix tratam o tema como requisito obrigatório.

O que é a idempotência em APIs?

Idempotência é a propriedade de uma operação que produz o mesmo resultado independentemente de quantas vezes for executada. Em APIs, significa que repetir a mesma requisição não cria efeitos colaterais adicionais: a segunda, terceira ou décima chamada retornam o mesmo estado da primeira, sem duplicar registros nem cobranças.

O conceito vem da matemática (uma função f é idempotente quando f(f(x)) = f(x)), mas o exemplo cotidiano ajuda mais: apertar o botão do elevador cinco vezes não o faz chegar mais rápido nem cinco vezes. O estado final (“elevador chamado”) é o mesmo.

Métodos HTTP idempotentes vs não idempotentes

A especificação HTTP já define quais métodos devem ser idempotentes:

Método Idempotente Seguro (não altera estado) Exemplo
GET buscar um cadastro
HEAD verificar se recurso existe
OPTIONS preflight de CORS
PUT substituir um cadastro inteiro
DELETE remover um registro
POST criar pedido, processar pagamento
PATCH ❌* atualização parcial

PUT é idempotente porque substitui o recurso pelo estado enviado: repetir a operação reescreve o mesmo estado. DELETE idem: apagar algo já apagado não muda nada, ainda que o status retornado mude de 200 pra 404.

Por que POST não é idempotente (e o caso do PATCH)

POST cria um recurso novo a cada chamada: dois POSTs de pedido geram dois pedidos. PATCH leva o asterisco porque depende do conteúdo: um patch “defina status como pago” é idempotente; um patch “incremente o saldo em 10” não é. Como a especificação não dá garantia, trate PATCH como não idempotente por padrão.

O problema real: timeout não significa falha

Em sistemas distribuídos, uma requisição que estoura o tempo limite tem três desfechos possíveis: nunca chegou ao servidor, chegou e falhou, ou chegou e foi processada com sucesso: só a resposta se perdeu. O cliente não tem como distinguir os três casos.

É por isso que retry sem idempotência é perigoso: se o terceiro caso aconteceu e o cliente reenvia o POST, a operação executa duas vezes. Backoff exponencial e circuit breaker controlam a frequência das tentativas, mas só a idempotência torna a repetição segura. Os dois andam juntos: retry define quando repetir; idempotência garante que repetir não causa estrago.

Estratégias para implementar idempotência em APIs de cadastro

Chave de idempotência (Idempotency key)

O padrão de mercado pra tornar POST idempotente é o header Idempotency-Key (objeto de uma especificação em andamento no IETF e adotado por Stripe, PayPal e outros). O fluxo:

  1. O cliente gera um UUID v4 e envia no header da requisição
  2. O servidor verifica se já processou essa chave
  3. Se não: processa, salva a resposta associada à chave e retorna
  4. Se sim: retorna a resposta salva, sem executar de novo
POST /v1/pagamentos HTTP/1.1
Idempotency-Key: 7f0c78e5-d51c-4634-b03b-2f770e8013dc
Content-Type: application/json

{ "valor": 14990, "cartao_token": "tok_abc123" }
Diagrama de sequência da Idempotency-Key com Redis: retry sem cobrança duplicada
Idempotency-Key na prática: o retry recebe a resposta salva, sem nova cobrança

Identificadores naturais de negócio

Nem sempre é preciso criar chave artificial. Se o domínio já tem identificador único (número do pedido, ID da transação no parceiro), use-o como chave de deduplicação com uma constraint UNIQUE no banco. A tentativa duplicada falha na inserção e o handler trata o conflito.

Fingerprint da requisição

Terceira opção: derivar a chave do próprio conteúdo, com hash do payload (SHA-256 do corpo + endpoint + usuário). Funciona pra deduplicar reenvios idênticos, mas tem armadilha: dois pedidos legítimos com o mesmo conteúdo (cliente compra o mesmo item duas vezes de propósito) seriam tratados como duplicata. Use só quando repetição idêntica for sempre erro.

Arquitetura de armazenamento para controle de idempotência em APIs

Redis: A escolha mais popular

O armazenamento das chaves precisa de escrita atômica (duas requisições simultâneas com a mesma chave não podem ambas “ganhar”) e expiração automática. Redis entrega os dois com SET NX EX:

async function processarComIdempotencia(chave, executar) {
  // tenta registrar a chave; NX = só se não existir
  const reservou = await redis.set(
    `idem:${chave}`, 'PROCESSANDO', 'NX', 'EX', 86400
  );

  if (!reservou) {
    const salvo = await redis.get(`idem:${chave}`);
    if (salvo === 'PROCESSANDO') {
      // outra requisição com a mesma chave está em andamento
      throw new ConflictError(409, 'Requisição em processamento');
    }
    return JSON.parse(salvo); // resposta já calculada
  }

  const resposta = await executar();
  await redis.set(`idem:${chave}`, JSON.stringify(resposta), 'EX', 86400);
  return resposta;
}

O estado intermediário PROCESSANDO resolve a condição de corrida: a segunda requisição concorrente recebe 409 e o cliente tenta de novo em seguida, quando a resposta já estará salva.

Definindo o tempo de retenção

A chave não precisa viver pra sempre: basta cobrir a janela de retry do cliente. 24 horas é o padrão da maioria dos provedores de pagamento; operações com reconciliação lenta podem pedir 7 dias. TTL curto demais reabre a porta da duplicidade; longo demais incha o armazenamento sem benefício.

Tratamento de erros e cenários especiais em idempotência em APIs

Os três erros da especificação

  • 400 Bad Request: a chave de idempotência está ausente quando o endpoint a exige, ou em formato inválido
  • 422 Unprocessable Entity: a chave já foi usada com um payload diferente — o cliente está reutilizando chave indevidamente
  • 409 Conflict: a requisição original com a mesma chave ainda está em processamento

O 422 merece atenção: comparar o fingerprint do payload com o da requisição original é o que impede um bug no cliente de receber a resposta de outra operação.

O problema do processamento parcial

E se o servidor falhar no meio do caminho, depois de cobrar o cartão mas antes de salvar o pedido? Salvar a resposta na chave de idempotência só no fim da transação garante que a repetição reexecute o fluxo inteiro. Por isso operações idempotentes devem embrulhar seus efeitos numa transação ou usar padrões como outbox: ou tudo aconteceu e a resposta está salva, ou nada valeu e o retry recomeça do zero.

Idempotência em pagamentos: Pix, cartões e Open Finance

Pagamento é o cenário onde duplicidade dói mais, e os padrões brasileiros já incorporam a proteção. No Pix, o txid identifica a cobrança de ponta a ponta: reenviar a criação com o mesmo txid não gera segunda cobrança. No Open Finance Brasil, a especificação de pagamentos exige o header x-idempotency-key nos POSTs de iniciação. Gateways como Stripe e provedores nacionais seguem o mesmo modelo de Idempotency-Key descrito acima.

A regra prática pra quem integra: gere a chave antes de iniciar o fluxo de pagamento, persista junto com o pedido e reutilize a mesma chave em todos os retries daquela operação. Chave nova a cada tentativa anula a proteção.

Idempotência em mensageria e filas

Filas como Kafka, SQS e RabbitMQ entregam mensagens com garantia at-least-once: pelo menos uma vez, o que inclui duas. Todo consumidor precisa ser idempotente: deduplique pelo ID da mensagem (mesma técnica do Redis acima) ou desenhe o processamento pra que repetir seja inofensivo, como gravações com chave natural única. O mesmo vale pra webhooks, que provedores reenviam quando não recebem confirmação.

Perguntas frequentes

O que é idempotência em APIs?

É a garantia de que repetir a mesma requisição produz o mesmo resultado, sem efeitos colaterais duplicados. Permite que clientes reenviem requisições após timeout ou erro de rede com segurança, sem risco de criar dois pedidos ou duas cobranças.

O que é uma chave de idempotência (Idempotency-Key)?

É um identificador único (geralmente UUID) que o cliente envia num header da requisição. O servidor usa a chave pra reconhecer repetições: a primeira chamada é processada e a resposta fica salva; as seguintes com a mesma chave recebem a resposta salva, sem reprocessar.

Quais métodos HTTP são idempotentes?

GET, HEAD, OPTIONS, PUT e DELETE são idempotentes por especificação. POST não é, e PATCH não tem garantia — depende da semântica da operação.

POST pode ser idempotente?

Pode, com implementação explícita: o padrão Idempotency-Key torna POSTs seguros pra retry. É assim que APIs de pagamento permitem reenviar a criação de uma cobrança sem duplicá-la.

Por que idempotência é importante em pagamentos e no Pix?

Porque a falha mais comum em pagamento é a resposta perdida: a cobrança foi feita, mas o cliente não soube. Sem idempotência, o retry cobra duas vezes. No Pix, o txid único da cobrança cumpre esse papel; no Open Finance, o header x-idempotency-key é obrigatório nos POSTs de iniciação de pagamento.

Conclusão

Idempotência é pré-requisito de qualquer API que processa operações com efeito colateral em rede não confiável. O caminho prático: aproveite a idempotência natural de PUT e DELETE, proteja os POSTs críticos com Idempotency-Key armazenada de forma atômica (Redis com SET NX e TTL de 24h), valide o fingerprint do payload pra detectar reuso indevido de chave e mantenha consumidores de fila e webhooks preparados pra mensagens repetidas. Com isso, retry deixa de ser risco e vira o que deveria ser: mecanismo de resiliência.

Leituras relacionadas

Compartilhe nas mídias: