Nordlet

← Documentacao / Glossario

O que é a idempotência? Por que as API de contabilidade e pagamentos precisam dela

Como um pedido repetido cria uma fatura duplicada, o que uma chave de idempotência garante, os modos de falha que deve gerir e como implementá-la corretamente.

Uma operação é idempotente se executá-la duas vezes produzir o mesmo efeito que executá-la uma vez. Ler um registo é naturalmente idempotente. Criar uma fatura não é - chame-a duas vezes e terá duas faturas, duas contas a receber e um cliente a perguntar por que razão foi faturado em duplicado.

Essa lacuna é importante porque as redes falham de uma forma específica e desagradável: o pedido é bem-sucedido e a resposta perde-se. O cliente deteta um timeout e não consegue distinguir se "nunca chegou" ou se "chegou, funcionou, mas a resposta perdeu-se". A sua única opção aparentemente segura é tentar novamente, e é essa nova tentativa que cria a duplicação.

Porque é que a contabilidade torna isto pior

Na maioria dos sistemas, uma duplicação é um incómodo. Num livro-razão (ledger), é um problema acumulado:

  • Uma fatura duplicada consome um número de uma sequência contínua, pelo que eliminá-la não é uma opção - a imutabilidade significa que corrigi-la exige uma nota de crédito, que é um documento visível para o cliente.
  • A duplicação lança registos no diário, pelo que a receita, o IVA e as contas a receber ficam todos sobreavaliados até alguém dar por isso.
  • Entra na declaração de IVA e, na Lituânia, nos registos do i.SAF.
  • Se acionou um pagamento, o dinheiro efetivamente mudou de mãos.

"Basta verificar se existem duplicados" também não funciona, porque duas faturas genuinamente separadas para o mesmo cliente, com o mesmo montante e no mesmo dia são perfeitamente normais. O sistema não consegue inferir a intenção a partir do conteúdo - o cliente tem de a declarar.

Como funcionam as chaves de idempotência

O cliente gera uma chave única por operação lógica e envia-a com o pedido:

POST /v1/sales/invoices/create
Idempotency-Key: 8f14e45f-ea1b-4c2b-9f3a-2d7c1e0b5a91

O servidor garante então o seguinte:

  1. Primeiro pedido com esta chave - processa normalmente e guarda o resultado associado à chave.
  2. Nova tentativa com a mesma chave e a mesma carga útil (payload) - não volta a executar; devolve a resposta guardada.
  3. Mesma chave, carga útil diferente - rejeita. A chave é uma afirmação de que se trata da mesma operação; um corpo diferente significa que não é, e devolver silenciosamente o resultado antigo seria pior do que um erro.
  4. Nova tentativa enquanto a original ainda está a correr - rejeita com um erro repetível em vez de executar em simultâneo.

O ponto 4 é aquele que as implementações simples ignoram. Um cliente cujo pedido expira aos 5 segundos e tenta novamente aos 6 vai colidir com o seu próprio pedido em curso; sem uma proteção de concorrência, ambos prosseguem e obtém-se a duplicação que o mecanismo existia para evitar.

Gerir chaves corretamente

A chave deve identificar a operação, e não a tentativa:

  • Certo: um UUID gerado uma única vez quando o utilizador clica em "emitir fatura", reutilizado em cada nova tentativa desse clique.
  • Certo: uma chave determinística derivada do seu próprio domínio - order-1041-invoice.
  • Errado: um novo UUID por tentativa HTTP. Cada nova tentativa obtém uma nova chave, pelo que cada nova tentativa é uma operação nova e não implementou nada.
  • Errado: um hash da carga útil (payload). Duas operações legitimamente idênticas entram então em colisão e a segunda é absorvida silenciosamente.

As chaves precisam de um período de retenção - o suficiente para cobrir janelas de repetição realistas e falhas do cliente, mas não para sempre.

O que a idempotência não resolve

A idempotência protege contra a execução duplicada do mesmo pedido. Não protege contra:

  • Dois utilizadores diferentes a criar a mesma fatura a partir de sessões diferentes - isso é um problema de regras de negócio, resolvido por restrições de unicidade, como uma fatura por encomenda.
  • Duplicados lógicos com chaves diferentes.
  • Não determinismo dentro da operação - se o gestor (handler) gerar um carimbo de data/hora ou um número, a resposta guardada captura o primeiro, que é o comportamento pretendido, mas convém compreender.

Combina-se com as restrições da base de dados, em vez de as substituir. O livro-razão (ledger) da Nordlet utiliza ambas: as chaves de idempotência impedem pedidos repetidos, enquanto os índices únicos impedem lotes de liquidação duplicados por ID de pagamento, faturas de compra duplicadas por fornecedor e número de documento, e execuções de depreciação duplicadas por ativo e período.

Como a Nordlet a implementa

Envie um cabeçalho Idempotency-Key em qualquer pedido que altere o estado - uma cadeia de caracteres (string) não vazia com um máximo de 255 carateres.

O primeiro pedido com uma determinada chave é executado e o seu resultado é guardado. Uma nova tentativa com a mesma chave e a mesma carga útil (payload) devolve a resposta guardada sem ser reexecutada, pelo que a fatura é criada exatamente uma vez, independentemente do número de vezes que a chamada seja repetida.

Dois modos de falha são explícitos em vez de silenciosos:

  • Mesma chave, carga útil diferente → rejeitada: "Idempotency-Key was already used with a different request payload." Isto deteta o caso genuinamente perigoso em que um cliente reutiliza uma chave enquanto altera o montante.
  • Mesma chave, pedido original ainda em curso → rejeitado como repetível: "A request with this Idempotency-Key is still being processed, retry shortly." Sem dupla execução concorrente.

As chaves são delimitadas por empresa (company), pelo que dois inquilinos (tenants) que gerem o mesmo UUID nunca entram em colisão.

A garantia complementar funciona no sentido inverso. A entrega de webhooks é efetuada pelo menos uma vez (at-least-once), o que significa que o seu endpoint receberá ocasionalmente o mesmo evento duas vezes - após um timeout em que a entrega foi efetivamente bem-sucedida, ou numa nova entrega. Cada evento transporta um ID, e o seu gestor (handler) deve registar os IDs processados e ignorar as repetições. A idempotência é uma propriedade de que ambos os lados de uma integração precisam; o guia de convenções da API aborda detalhadamente a vertente dos pedidos.

FAQ

O que é uma chave de idempotência?

Um identificador gerado pelo cliente e enviado com um pedido que permite ao servidor reconhecer uma nova tentativa da mesma operação lógica. O primeiro pedido é executado; as novas tentativas com a mesma chave devolvem o resultado guardado em vez de executarem novamente.

Porque é que as API de contabilidade precisam de idempotência?

Porque uma escrita duplicada cria um documento real com consequências reais - uma segunda fatura que consome um número sequencial, é lançada no livro-razão (ledger) e entra numa declaração de IVA. Ao contrário de uma leitura duplicada, não pode simplesmente ser eliminada; tem de ser corrigida com uma nota de crédito.

Devo gerar uma nova chave para cada nova tentativa?

Não - isso anula completamente o mecanismo. Gere uma chave por operação lógica e reutilize-a em cada nova tentativa dessa operação. Uma nova chave por tentativa significa que cada tentativa é tratada como uma nova operação.

O que acontece se eu reutilizar uma chave com uma carga útil diferente?

O pedido é rejeitado. A chave afirma que esta é a mesma operação, pelo que um corpo alterado significa que algo está errado - muito provavelmente um erro de reutilização da chave no cliente. Devolver o resultado anterior de forma silenciosa seria pior.

A idempotência evita todos os duplicados?

Não. Evita a execução duplicada do mesmo pedido. Dois utilizadores diferentes que criam independentemente a mesma fatura fizeram dois pedidos diferentes, o que constitui um problema de regras de negócio para restrições de unicidade, e não algo que uma chave de idempotência consiga detetar.