Skip to main content
Seu backend é o único chamador que repete sozinho. Um cliente HTTP estoura o timeout e tenta de novo, uma fila reentrega, um deploy replica um lote que ficou pela metade. A requisição chega duas vezes porque o transporte está fazendo o trabalho dele. Toda escrita na superfície da chave secreta aceita o header Idempotency-Key. Envie um, e a repetição recebe a mesma resposta de volta — mesmo status, mesmo corpo — em vez de executar outra vez.
201
Envie de novo, byte a byte, e você recebe o mesmo 201 com created: true:
Sem a chave, essa segunda chamada responde 200 com created: false — correto, e inútil para o código que a fez. Um programa que provisiona um workspace quando created é verdadeiro não consegue distinguir “outra pessoa já fez isso” de “a minha própria repetição passou”.

Escolhendo uma chave

Qualquer valor opaco, até 255 caracteres. Um UUID por tentativa é a resposta usual; o seu próprio identificador da operação (order_8421-refund) também funciona, e tem a vantagem de que um processo que recomeça do zero gera o mesmo. A chave é escopada pela sua chave de API e pela rota. Dois inquilinos enviando Idempotency-Key: 1 nunca colidem, uma chave live nunca repete o que uma chave test respondeu, e a mesma chave em outro endpoint é outra operação. As chaves são lembradas por 24 horas. Toda repetição que algum dia vai carregar uma delas acontece dentro dessa janela — o backoff de um cliente é de segundos, a reentrega de uma fila é de minutos, uma pessoa reexecutando um job que falhou é de horas. Um dia depois você não está repetindo, está chamando de novo, e deve enviar uma chave nova.

As três respostas que uma repetição pode receber

A primeira requisição terminou. Você recebe o status e o corpo dela, mais Idempotent-Replay: true. O handler não roda.
Outra requisição com essa chave está em voo. Exatamente uma delas chega ao handler, por construção — então isso não é uma corrida que você perdeu, é a corrida sendo fechada. Espere o Retry-After e tente de novo; você vai receber a resposta repetida.
409
A chave já está registrada com outro corpo ou outra query string. Repetir a primeira resposta aqui diria que uma chamada deu certo quando ela nunca aconteceu, então ela é recusada.
422
Isso quase sempre significa uma chave reaproveitada sem querer — uma constante esquecida no código, ou uma repetição que remontou o payload a partir de um estado que mudou. Gere uma chave por operação, não por processo.

Falha não é resultado

Uma requisição que respondeu 5xx, ou que foi barrada pelo limite de taxa antes de chegar ao handler, não deixou nada para trás. A chave dela volta para a prateleira: repita com a mesma chave e a requisição executa. Uma requisição que respondeu 201 criou algo, e a chave dela está gasta. A que respondeu 400 também — o mesmo corpo vai produzir o mesmo 400, e repeti-lo não custa nada a você. Essa assimetria é a regra inteira, e é o que torna correto o laço de repetição ingênuo:
1

Gere uma chave para a operação

Antes da primeira tentativa, não dentro dela.
2

Envie em todas as tentativas

Mesma chave, mesmo corpo.
3

Trate 409 como 'ainda não'

Faça backoff e tente de novo. Trate 422 como um bug do seu próprio código — uma chave nova não conserta uma chave reaproveitada, esconde.

Onde vale

Só na superfície da chave secreta, uk_sk_…. Leituras ignoram o header: repetir um GET devolveria uma resposta velha para uma pergunta que você fez justamente porque queria uma nova. As superfícies chamadas do navegador — /v1/boot, /v1/contact-auth/*, /v1/contact/* — não aceitam o header. Elas são dirigidas por uma pessoa, e os fluxos que poderiam duplicar já são de uso único por construção: um magic link, um código por e-mail e um callback social são gastos pela primeira requisição que os resgata.
POST /v1/contacts resolve para a mesma pessoa, seja chamado uma ou dez vezes — valores identificadores nunca reapontam para um segundo contato. A chave é o que torna a resposta estável também, e é nela que o seu código de fato ramifica.