Idempotência
Como repetir um pedido sem ser cobrado duas vezes.
Se a conexão cair no meio de "gerar keys", o seu sistema não sabe se as keys saíram. Repetir o pedido sem proteção geraria e cobraria de novo.
Por isso Gerar keys exige o cabeçalho Idempotency-Key: um código único que você cria para cada pedido. Repetindo com o mesmo código, a API devolve as mesmas keys e não cobra outra vez.
Idempotency-Key: 3f1c9e2a-7b4d-4c1e-9a0f-5d2b8c6e1a47O código
- Um UUID serve, e é o mais simples (
crypto.randomUUID(),uuid.uuid4()). - Até 100 caracteres: letras, números e
_ - : .— sem espaço nem acento. - Um código por venda, criado antes de chamar a API e guardado junto dela.
O que acontece ao repetir
| Situação | Resposta |
|---|---|
| Mesmo código e mesmo pedido, e o primeiro já terminou | As mesmas keys, sem cobrar, com "idempotent_replay": true e o cabeçalho Idempotent-Replayed: true. |
| Mesmo código, e o primeiro ainda está rodando | 409 — espere alguns segundos e repita. |
| Mesmo código com pedido diferente (outro produto, quantidade ou duração) | 409 — use um código novo para um pedido novo. |
| O primeiro falhou (sem saldo, dados inválidos, erro) | Nada foi cobrado; o código fica livre para tentar de novo. |
Reuse o código só para repetir o mesmo pedido. Nunca crie um código novo para repetir: aí a API entende que é outra venda e cobra de novo.
Exemplo de repetição segura
async function generateKeys(order) {
// O código nasce com o pedido e fica salvo nele.
order.idempotencyKey ??= crypto.randomUUID();
await saveOrder(order);
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const res = await fetch(`${process.env.PROJECT_API_URL}/keys`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PROJECT_TOKEN}`,
'Content-Type': 'application/json',
Accept: 'application/json',
'Idempotency-Key': order.idempotencyKey,
},
body: JSON.stringify({ product_id: order.productId, quantity: order.quantity, days: order.days }),
signal: AbortSignal.timeout(30_000),
});
// 5xx e 409 (ainda rodando): espera e repete com o MESMO código.
if (res.status >= 500 || res.status === 409) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (error) {
if (attempt === 3) throw error;
await new Promise((resolve) => setTimeout(resolve, attempt * 2000));
}
}
}