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:

Obtenha Acesso Imediato a todos WebServices!

Tenha acessos a todos os dados e API de WS.

Destaques: