Definição
A idempotency key (chave de idempotência) é um identificador único que um cliente envia junto com uma requisição mutante (tipicamente um POST) para garantir que, em caso de retry de rede, a operação seja executada apenas uma vez.
O servidor armazena a chave; se a mesma requisição voltar, ele devolve a resposta anterior sem reexecutar a operação. É uma best practice crítica para qualquer API de pagamento, em que uma dupla execução significa pagamento em dobro, cobrança em dobro ou estado corrompido. Stripe, Bridge, Adyen, Mollie, AWS e muitos outros a exigem ou a suportam.
Por que é crítica
Na rede, a incerteza é a regra: uma requisição pode expirar sem que se saiba se foi recebida, processada ou se um retry vai duplicar a operação. Sem chave de idempotência, é impossível fazer retry com segurança; com ela, o cliente pode repetir tranquilamente, e o servidor devolve a resposta armazenada se a operação já tiver ocorrido.
Como funciona
Formato e boas práticas
- Geração: UUID v4 (aleatório), ULID (ordenado no tempo, debug-friendly) ou hash de negócio determinístico (
SHA256(user_id + transaction_ref)). - Header:
Idempotency-Key: 4a7f3b80-fe5c-4d12-9c7e-83b3a4ab9b1c. A IETF publicou um draft, mas a convenção foi adotada bem antes de sua oficialização. - Duração do cache: 24h na Stripe e na Bridge, 1h na Mollie. Recomendado: 24 a 48h para pagamentos, 7 a 30 dias para fluxos assíncronos. Além disso, um retry reexecutaria a operação.
- Escopo: por lojista / por conta (recomendado), em vez de global (risco de colisão).
Payload diferente, mesma chave
Se a mesma chave for enviada com um payload diferente, o servidor deveria rejeitar a requisição (409 Conflict), como fazem a Stripe e a AWS. Isso evita que um payment 200 € seja silenciosamente ignorado após um payment 100 € com a mesma chave.
Casos de uso
- Pagamentos:
POST /payments,/transfers,/charges,/refunds— sempre. - Criação de recursos:
POST /customers,/subscriptions— para evitar duplicatas. - Operações custosas:
POST /kyc,/loans/disburse. - Desnecessária em:
GET(idempotente por natureza),PUTcom ID explícito,DELETE.
Idempotency-Key vs outras técnicas
| Técnica | Garantia | Complexidade |
|---|---|---|
| Idempotency-Key (header) | Exactly-once por chamada | Baixa do lado do cliente |
| Token de transação único | Exactly-once por transação de negócio | Média |
| Lock otimista de BD | Exactly-once por recurso | Média |
| Saga + commands | Exactly-once por fluxo | Alta |
A Idempotency-Key atua no nível da API: ela não protege contra um bug de negócio (um usuário que paga duas vezes por meio de dois botões diferentes gera duas chaves distintas).
Implementação do lado do servidor
Armazenamento típico: Redis (TTL nativo, chave idempotency:{merchant}:{key}), DynamoDB para escalabilidade ou PostgreSQL em baixo volume.
def handle_payment(req):
key = req.headers.get("Idempotency-Key")
if not key:
return create_payment(req)
cached = redis.get(f"idempotency:{merchant}:{key}")
if cached:
return cached
with redis.lock(f"idempotency-lock:{merchant}:{key}", ttl=30):
cached = redis.get(f"idempotency:{merchant}:{key}")
if cached:
return cached
response = create_payment(req)
redis.setex(f"idempotency:{merchant}:{key}", 86400, response)
return responseO lock é necessário se duas requisições com a mesma chave chegarem ao mesmo tempo (race condition de retry), para evitar uma dupla execução em paralelo.
Comportamento em caso de falha
- Timeout do cliente, sucesso no servidor: retry com a mesma chave → resposta em cache (sucesso).
- Erro 5xx: a maioria dos provedores reexecuta (um 5xx não é considerado definitivo).
- Erro 4xx: retry com a mesma chave → a resposta 4xx em cache é devolvida.
O que a Idempotency-Key não é
- Não é uma transação distribuída: ela não garante a consistência entre serviços.
- Não é uma proteção de negócio: dois cliques em dois botões geram duas chaves.
- Não é uma substituição da autenticação: é complementar.
- Não é necessária em GET / PUT: nativamente idempotentes.
- Não é (ainda) um padrão formal: draft IETF, mas convenção quase universal.
No ecossistema PSD2 / Open Finance
- Berlin Group: recomenda
X-Request-ID(equivalente). - STET: header
X-Request-ID. - OBIE UK:
x-idempotency-key. - PSPs adquirentes (Stripe, Adyen, Mollie): adotada universalmente.
- Webhooks: a chave também é crucial na recepção, para não processar duas vezes um mesmo evento.
Exemplos concretos
- Stripe: header
Idempotency-Key, cache de 24h, rejeição de payload diferente — o padrão do setor. - Bridge:
Idempotency-Key+X-Request-IDconforme o endpoint. - Mollie:
Idempotency-Key, cache de 1h. - AWS: parâmetro
client-token. - Caso vivido: um lojista sem chave de idempotência faz retry após um timeout → débito em dobro em 0,5% dos pagamentos → dezenas de milhares de euros em estornos e perda de confiança.
- Padrão de UI: gerar a chave no momento do clique e persisti-la no state do formulário — um novo clique 5 segundos depois reutiliza a mesma chave, sem cobrança em dobro.