Idempotência em APIs que movem dinheiro e decisão
Por que toda API que cobra, credita ou decide precisa de idempotência: o que a RFC 9110 define, como a chave de idempotência funciona e os detalhes que separam proteção real de decorativa.
Aperte o botão de pagar e a tela congele: o pagamento aconteceu? Se a política do sistema for repetir a chamada e torcer, o usuário pode pagar duas vezes. Se for nunca repetir, pode não pagar nenhuma. Idempotência é a propriedade que desarma esse dilema: uma operação idempotente produz o mesmo estado final seja executada uma vez ou cinco, e por isso pode ser repetida com segurança diante de qualquer dúvida.
Em APIs de verificação de identidade o dilema aparece nas duas pontas: na criação de recursos que custam dinheiro, como uma verificação cobrada ou um crédito debitado, e no consumo de webhooks, que chegam mais de uma vez por desenho. Este artigo cobre a teoria mínima e os detalhes de implementação que separam uma proteção real de uma decorativa.
O que o protocolo já garante, e o que não garante
O HTTP tem o conceito formalizado. A RFC 9110 do IETF, a especificação vigente da semântica do HTTP, define como idempotentes o PUT, o DELETE e os métodos seguros, entre eles GET e HEAD: repetir a mesma requisição tem, no servidor, o mesmo efeito pretendido que fazê-la uma vez. POST fica de fora da lista, e é justamente com POST que se criam verificações, se disparam cobranças e se movimenta estado.
Método seguro, na definição da mesma RFC, é aquele cuja semântica é essencialmente somente leitura: o cliente não pede nem espera mudança de estado no servidor. A armadilha correspondente é conhecida: um GET que aprova, cancela ou reenvia alguma coisa quebra o contrato do protocolo, e qualquer intermediário que repita ou faça cache da requisição, comportamento permitido para método seguro, dispara o efeito sem ninguém pedir.
A consequência prática do POST: quando a chamada falha por timeout, o cliente não sabe se o servidor processou. A rede pode ter engolido a resposta depois de o trabalho estar feito. Sem um mecanismo adicional, o cliente escolhe entre repetir e arriscar duplicata, ou desistir e arriscar perda. O resto deste artigo é sobre esse mecanismo adicional.
A chave de idempotência
O padrão consolidado pela prática da indústria é o cabeçalho de chave de idempotência: o cliente gera um identificador único para a operação, tipicamente um UUID, e o envia junto da requisição. O servidor guarda a chave com o resultado da primeira execução; se a mesma chave voltar, devolve o resultado guardado em vez de executar de novo. A repetição vira consulta, e repetir passa a ser seguro.
O desenho está em processo de padronização formal: o grupo de trabalho HTTPAPI do IETF mantém o rascunho do cabeçalho Idempotency-Key, que documenta a semântica que provedores de pagamento praticam há anos. A documentação pública da Stripe, referência da indústria no tema, descreve o mesmo contrato, com as chaves expirando após 24 horas.
Os detalhes que separam a implementação real da decorativa:
- a chave e o efeito são gravados na mesma transação de banco: checar a chave e depois executar em passos separados abre a corrida clássica em que duas requisições simultâneas passam juntas pela checagem e o efeito duplica;
- o resultado completo é guardado, não apenas uma marca de executado: a repetição precisa receber a mesma resposta da primeira execução, inclusive quando essa resposta foi um erro de negócio;
- mesma chave com corpo diferente é erro explícito, nunca reexecução silenciosa: o rascunho do IETF recomenda rejeitar a requisição, porque duas operações diferentes sob a mesma chave indicam defeito no cliente;
- a chave tem escopo e prazo documentados: vale por rota e por conta, com janela de retenção conhecida, para que o cliente saiba exatamente o que uma repetição tardia encontra.
O outro lado: consumir webhooks sem duplicar efeito
A entrega de webhooks é, por desenho, ao menos uma vez: retentativas legítimas fazem o mesmo evento chegar repetido, como detalha o artigo sobre webhooks seguros em KYC. O consumidor idempotente registra o identificador único do evento no mesmo ato transacional que aplica o efeito, e ignora silenciosamente o identificador já visto.
Quando o efeito é financeiro, a regra aperta. O princípio que adotamos na UNIFOKAL é que só o webhook confirmado credita, num único ponto do código: resposta síncrona de API não movimenta dinheiro, porque resposta síncrona é justamente a que se perde no timeout, e uma reconciliação periódica confere o estado local contra a plataforma para fechar qualquer fresta que sobre.
Onde a falta de idempotência cobra a conta
Dois momentos operacionais expõem quem não fez o dever de casa. O primeiro é o reprocessamento: um incidente derruba o consumidor de webhooks por horas, a fila acumula e precisa ser reprocessada. O sistema idempotente repete tudo sem medo; o outro escolhe entre auditar efeito por efeito na mão e aceitar duplicatas. O segundo é a migração de fornecedor, em que rodagem paralela e reenvio de eventos históricos são rotina do plano de corte, e cada efeito precisa reconhecer o que já foi aplicado antes.
Há ainda o caso silencioso do usuário impaciente: o clique duplo no botão de enviar é a forma mais barata de teste de idempotência que existe, e acontece em produção todos os dias, sem aviso e sem dó.
A regra de bolso para revisar o seu código: para cada endpoint que escreve, pergunte o que acontece se esta exata requisição executar duas vezes seguidas. Se a resposta honesta contiver a palavra esperamos, ainda falta idempotência. A pergunta se aplica igualmente ao lado que você consome: um fornecedor sério documenta o comportamento de repetição de cada endpoint e o identificador único de cada evento de webhook, e a ausência dessa documentação é um dado sobre o fornecedor.
Fontes citadas
- RFC 9110, HTTP Semantics, do IETF, seção sobre métodos idempotentes: rfc-editor.org
- The Idempotency-Key HTTP Header Field, rascunho do grupo de trabalho HTTPAPI do IETF: datatracker.ietf.org
- Documentação pública de idempotência da Stripe, citada como referência de prática de indústria