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-5d2b8c6e1a47

O 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çãoResposta
Mesmo código e mesmo pedido, e o primeiro já terminouAs mesmas keys, sem cobrar, com "idempotent_replay": true e o cabeçalho Idempotent-Replayed: true.
Mesmo código, e o primeiro ainda está rodando409 — 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

JavaScript
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));
    }
  }
}

Nesta página