Pular para o conteúdo
xFlow Payments

Blog

Idempotência em API de pagamento: retry sem duplicar

Idempotência é a garantia de que repetir a mesma requisição não repete o efeito. Na API da xFlow, você envia o header Idempotency-Key ao criar cobranças e saques: num retry com a mesma chave, a API devolve a mesma cobrança ou o mesmo saque, sem gerar outro Pix nem debitar o saldo de novo.

Atualizado em · Equipe xFlow Payments · 6 min de leitura

Idempotência, numa API de pagamento, é a garantia de que enviar a mesma requisição duas vezes não cobra nem paga duas vezes. Na API Pix da xFlow, você envia o header Idempotency-Key com um valor único por operação ao criar uma cobrança (POST /api/v1/charges) ou pedir um saque (POST /api/v1/transfers). Se a requisição for repetida com a mesma chave e o mesmo corpo, a API devolve a mesma cobrança ou o mesmo saque. Se a chave vier com um corpo diferente, a resposta é 409 idempotency_conflict.

Por que o retry pode duplicar um pagamento

Redes falham no pior momento. Imagine este fluxo:

  1. O seu servidor envia POST /charges para o pedido 1234.
  2. A xFlow cria a cobrança e responde.
  3. A resposta se perde no caminho, ou o seu cliente HTTP desiste por timeout.
  4. O seu código não sabe se deu certo e tenta de novo.

Sem idempotência, o passo 4 cria uma segunda cobrança Pix para o mesmo pedido. O cliente pode receber dois códigos, pagar os dois, e você terá um estorno para fazer e um cliente insatisfeito para atender.

No saque, o risco é maior. Um retry sem proteção pode debitar o saldo duas vezes e mandar dinheiro duas vezes para a mesma chave Pix. Por isso a documentação recomenda o header nos dois casos e diz, sobre o saque, que no retry o mesmo saque é devolvido em vez de debitar o saldo de novo.

Como a xFlow trata cada operação

Operação Proteção contra duplicidade
POST /api/v1/charges (criar cobrança) Header Idempotency-Key, opcional e recomendado
POST /api/v1/transfers (saque Pix) Header Idempotency-Key, opcional e recomendado, único por saque
POST /api/v1/charges/{id}/refund (estorno) Idempotente por cobrança: repetir devolve o mesmo estorno; um estorno failed pode ser pedido de novo
GET (consultas e listagens) Só leem dados; repetir não muda nada
Webhooks (no seu servidor) Cada evento pode ser reenviado até 3 vezes; o seu handler precisa tolerar repetição

As três respostas possíveis

Com o header Idempotency-Key: pedido-1234:

  • Primeira chamada. A cobrança é criada e a API responde 201.
  • Mesma chave, mesmo corpo. A API devolve a mesma cobrança, com o mesmo id e o mesmo pix.copyPaste. Nenhum Pix novo é gerado.
  • Mesma chave, corpo diferente. A API responde 409 idempotency_conflict. É um aviso de bug: a chave está sendo reaproveitada para outra operação.
bash
curl -X POST https://app.xflowpayments.com/api/v1/charges \
  -u "pk_live_...:sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1234" \
  -d '{
    "amountCents": 1990,
    "customer": { "name": "Cliente Exemplo", "email": "cliente@exemplo.com", "document": "00000000000" },
    "description": "Pedido #1234",
    "external_reference": "pedido-1234"
  }'

Rode o mesmo comando duas vezes: o id da cobrança será o mesmo nas duas respostas.

Como escolher a chave

  • Derive a chave da operação, não da tentativa. Use o id do pedido (pedido-1234) ou do saque (saque-987). A chave precisa ser a mesma em todas as tentativas da mesma operação.
  • Nunca gere uma chave aleatória a cada tentativa. Isso anula a proteção: cada retry vira uma operação nova.
  • Monte o corpo uma vez. Se o corpo mudar entre tentativas (um timestamp, um campo recalculado), a segunda chamada recebe 409.
  • Operação nova, chave nova. Se o cliente desistiu e voltou com outro valor, ou se a cobrança anterior expirou, crie uma chave nova, como pedido-1234-2.

Exemplo: retry seguro em Node.js

O exemplo repete a chamada em falha de conexão, timeout, 429 e erros 5xx, sempre com a mesma chave e o mesmo corpo. No 429, espera o tempo do header retry-after, como a documentação orienta.

js
const BASE = "https://app.xflowpayments.com/api/v1";
const cred = Buffer.from(
  process.env.XFLOW_PUBLIC_KEY + ":" + process.env.XFLOW_SECRET_KEY,
).toString("base64");
const esperar = (ms) => new Promise((r) => setTimeout(r, ms));

export async function criarCobranca(pedido, tentativas = 4) {
  const chave = `pedido-${pedido.id}`; // a mesma em todas as tentativas
  const body = JSON.stringify({        // montado uma vez, fora do laço
    amountCents: pedido.valorCentavos,
    customer: pedido.cliente,
    description: `Pedido #${pedido.id}`,
    external_reference: chave,
  });

  for (let i = 1; i <= tentativas; i++) {
    try {
      const res = await fetch(`${BASE}/charges`, {
        method: "POST",
        headers: {
          Authorization: "Basic " + cred,
          "Content-Type": "application/json",
          "Idempotency-Key": chave,
        },
        body,
        signal: AbortSignal.timeout(15_000),
      });

      if (res.status === 429) {
        await esperar(Number(res.headers.get("retry-after") ?? 1) * 1000);
        continue;
      }
      if (res.status >= 500) throw new Error(`HTTP ${res.status}`);

      const json = await res.json();
      if (!res.ok) throw Object.assign(new Error(json.error), { naoRepetir: true, json });
      return json; // 201 na primeira vez, a mesma cobrança nas seguintes
    } catch (err) {
      if (err.naoRepetir || i === tentativas) throw err;
      await esperar(2 ** i * 250); // backoff exponencial
    }
  }
  throw new Error("Limite de tentativas atingido");
}

Erros 4xx como 400 validation e 409 idempotency_conflict não são repetidos: repetir não muda o resultado. Guarde o request_id de cada resposta. Ele aparece em Integrações > Logs da API, com status e duração de cada chamada, e ajuda a reconstruir o que aconteceu num incidente.

Idempotência no saque Pix

No saque, a chave protege o seu saldo. Gere um valor único quando o saque é registrado no seu sistema e repita esse valor em todas as tentativas:

bash
curl -X POST https://app.xflowpayments.com/api/v1/transfers \
  -u "pk_live_...:sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: saque-1234" \
  -d '{ "amount": 500, "pix_key": "12345678909", "pix_key_type": "CPF" }'

Se a resposta não chegar, repita a mesma chamada com a mesma chave. A API devolve o mesmo saque, com o mesmo id, em vez de debitar o saldo de novo. Para conferir o resultado sem criar nada, consulte GET /api/v1/transfers/{id}, se já tiver o id, ou liste os saques do período com GET /api/v1/transfers.

Um detalhe evita retry desnecessário: quando o saque automático está desligado na conta, o saque nasce pendente de aprovação (pending_approval: true). Isso não é falha. O saque foi criado e aguarda a aprovação.

Idempotency-Key não é external_reference

Os dois campos parecem parecidos, mas resolvem problemas diferentes:

Idempotency-Key external_reference
Onde vai Header HTTP Corpo da cobrança
Precisa ser único Sim, por operação Não
Para que serve Repetir a chamada sem duplicar Ligar a cobrança ao pedido no seu sistema
Uso na API Proteção na criação Filtro: GET /api/v1/charges?external_reference=pedido-1234

Use os dois. O external_reference também ajuda quando você não sabe se uma cobrança chegou a ser criada: consulte a listagem por ele antes de decidir o que fazer.

Do outro lado: o seu webhook também precisa ser idempotente

A idempotência vale nos dois sentidos. A xFlow reenvia cada webhook até 3 vezes quando não recebe 2xx, e uma entrega pode chegar repetida. Antes de dar baixa, confira se o pedido já foi processado, usando o data.id da cobrança e o nome do event. O handler completo está em webhook Pix.

No saque, a regra é a mesma do pedido: uma chave por saque. Veja os detalhes na API de saque Pix. Para provar que o seu retry funciona antes de ir ao ar, use o sandbox da API Pix, onde a idempotência funciona igual. Chaves, logs e guias por linguagem estão em Desenvolvedores.

Perguntas frequentes

O que é Idempotency-Key?

É um header HTTP com um valor único por operação, como o id do pedido. Se a mesma requisição for enviada de novo com a mesma chave, a API devolve o resultado da primeira em vez de executar outra vez.

Quais endpoints da xFlow aceitam Idempotency-Key?

A documentação indica o header na criação de cobrança (POST /api/v1/charges) e na solicitação de saque (POST /api/v1/transfers). O estorno já é idempotente por cobrança: repetir o pedido devolve o mesmo estorno.

O que significa o erro 409 idempotency_conflict?

Que a Idempotency-Key já foi usada com um corpo diferente. A mesma chave só vale para repetir o mesmo pedido. Para uma operação nova, use uma chave nova.

Idempotência funciona no ambiente de teste?

Sim. O comportamento é igual com chave pk_test_, inclusive o 409 idempotency_conflict, então você pode testar o seu retry no sandbox.

Continue lendo

SoluçãoAPI PixA API Pix da xFlow Payments é uma API REST para criar cobranças Pix com QR Code e copia e cola, estornar, sacar e receber webhooks assinados. Uma única integração conecta a sua operação a 11 adquirentes, com roteamento por regras e retentativa automática.BlogWebhook Pix: eventos, assinatura HMAC e boas práticasWebhook Pix é o aviso que a API envia para a sua URL quando algo acontece com uma cobrança ou um saque, como o pagamento confirmado. Na xFlow, cada aviso é um POST JSON assinado com HMAC-SHA256, reenviado até 3 vezes até você responder 2xx.BlogAPI de saque PixNa API da xFlow, o saque Pix (Pix out) é um POST /api/v1/transfers com amount em centavos e a chave do destinatário (pix_key e pix_key_type) ou um qr_code copia e cola. O saque nasce com status requested, pode exigir aprovação (pending_approval) e é acompanhado pelos webhooks withdrawal.* ou por GET /api/v1/transfers/{id}. Não funciona com chave de teste.BlogAPI Pix sandbox: como testar sem mover dinheiroO sandbox da API Pix da xFlow funciona com chaves pk_test_ e sk_test_: mesmos endpoints, mesmas respostas com livemode: false, nenhum dinheiro real e um endpoint para simular o pagamento que dispara o webhook transaction.paid.DesenvolvedoresA documentação da API de pagamentos Pix da xFlow é aberta e fica em app.xflowpayments.com/docs, com uma versão em texto para assistentes de IA no llms.txt. Esta página reúne o caminho para integrar: chaves, sandbox, webhooks, logs e guias técnicos.