Pagamento Pix em lote é enviar vários Pix de uma vez, por exemplo para pagar fornecedores ou repassar valores a parceiros no fim do dia. Na API da xFlow, a documentação descreve uma transferência por chamada: POST /api/v1/transfers, com um destino e um valor. Não há endpoint que receba uma lista. O lote, então, vive no seu sistema: uma fila de itens, um saque por item, cada um com a sua Idempotency-Key, e uma conferência no fim. Se você ainda não usou o saque pela API, comece pelo guia de API de saque Pix.
Por que montar o lote item por item
Enviar um saque por item parece mais trabalhoso, mas dá ao seu sistema controle sobre cada pagamento:
- Cada item tem o seu status. Um favorecido com chave errada não trava os outros.
- O retry é por item. Se a conexão cair no item 37, você repete só o 37, sem risco de pagar os outros de novo.
- Cada pagamento é rastreável. O saque tem
idpróprio e cada resposta traz umrequest_id, que aparece em Integrações > Logs da API.
Antes de rodar o lote
- Chave de produção. O saque não funciona com chave de teste: a resposta é
403 test_mode_unsupported. - Saque via API habilitado e conta ativa. Sem isso, as respostas são
403 api_withdrawal_disabledou403 account_inactive. - Saldo suficiente. Consulte
GET /api/v1/balancee compare oavailablecom a soma do lote. As taxas de saque da conta vêm emGET /api/v1/company, nos camposwithdrawalPercentewithdrawalFixedCents. - Dados de cada favorecido. Para saque por chave, você precisa de
pix_keyepix_key_type:CPF,CNPJ,EMAIL,PHONEouEVP(chave aleatória, formato UUID). São os tipos do diretório de chaves do Pix, o DICT. Valide o formato antes de enviar, para não gastar chamada com400 validation. - Quanto o favorecido recebe. No saque por chave, a taxa sai do próprio
amount, e o favorecido recebe onet_amount. No saque por QR Code, o favorecido recebe o valor do QR e a taxa é debitada por cima. Se o fornecedor precisa receber o valor exato da nota, peça o QR Code de cobrança dele ou calcule oamountcom a taxa incluída e confira onet_amountna resposta. Nem todo provedor aceita QR: quando não aceita, a resposta é422 transfer_failede nada é debitado.
O modelo de dados do lote
Grave o lote antes de enviar qualquer coisa. Uma tabela de itens resolve:
| Campo | Para que serve |
|---|---|
idempotency_key |
Chave estável do item, como lote-20261025-item-0037 |
pix_key e pix_key_type |
Destino do pagamento |
valor_centavos |
O amount enviado à API |
transfer_id |
O id devolvido pela API; vazio enquanto não foi enviado |
status |
O último status conhecido do saque |
request_id |
O identificador da chamada, para os Logs da API e o suporte |
erro |
O código de erro, quando houver |
A chave precisa ser a mesma em todas as tentativas do mesmo item. Não use horário nem número aleatório na chave: na repetição, a chave tem de bater com a primeira tentativa. Por que isso importa está em idempotência em API de pagamento.
O envio, item por item
O exemplo abaixo, em Node.js 18 ou superior, envia os itens em sequência. Ele repete falhas de conexão e respostas 5xx com a mesma chave, espera o retry-after no 429 e grava o resultado de cada item.
const BASE = "https://app.xflowpayments.com/api/v1";
const AUTH = "Basic " + Buffer.from(
`${process.env.XFLOW_PUBLIC_KEY}:${process.env.XFLOW_SECRET_KEY}`,
).toString("base64");
const espera = (ms) => new Promise((ok) => setTimeout(ok, ms));
async function enviarItem(item) {
for (let tentativa = 1; tentativa <= 5; tentativa++) {
let res;
try {
res = await fetch(`${BASE}/transfers`, {
method: "POST",
headers: {
Authorization: AUTH,
"Content-Type": "application/json",
"Idempotency-Key": item.idempotencyKey, // a mesma em toda tentativa
},
body: JSON.stringify({
amount: item.valorCentavos,
pix_key: item.pixKey,
pix_key_type: item.pixKeyType,
message: item.mensagem,
}),
});
} catch {
await espera(2 ** tentativa * 1000); // falha de conexão: tenta de novo
continue;
}
const requestId = res.headers.get("x-request-id");
if (res.status === 429) {
await espera(Number(res.headers.get("retry-after") ?? 5) * 1000);
continue;
}
if (res.status >= 500) {
await espera(2 ** tentativa * 1000);
continue;
}
const corpo = await res.json();
if (res.ok) return { ok: true, saque: corpo.data, requestId };
return { ok: false, erro: corpo.error, mensagem: corpo.message, requestId };
}
return { ok: false, erro: "sem_resposta" };
}
async function rodarLote(itens) {
for (const item of itens) {
if (item.transferId) continue; // já enviado numa execução anterior
const resultado = await enviarItem(item);
await gravarResultado(item, resultado); // grava transfer_id, status, request_id ou erro
}
}Enviar em sequência é o jeito mais simples de respeitar o limite de requisições, que é por conta. Se precisar de paralelismo, limite o número de envios simultâneos e acompanhe os cabeçalhos x-ratelimit-remaining e x-ratelimit-reset, presentes em toda resposta autenticada.
Acompanhamento: status e webhooks
Cada saque tem um destes status: requested, processing, completed ou rejected. Se o saque automático estiver desligado na conta, cada item nasce com pending_approval: true e espera aprovação antes de seguir.
Assine os webhooks withdrawal.processing, withdrawal.completed e withdrawal.failed. A documentação não detalha o data desses eventos, então use o webhook como gatilho e confirme o estado com GET /api/v1/transfers/{id}, usando o transfer_id que você gravou. Para conferir o lote inteiro de uma vez, liste com GET /api/v1/transfers?from=2026-10-25&to=2026-10-25 e siga o cursor starting_after enquanto has_more for true.
Conferência no fim do lote
| Resultado do item | O que fazer |
|---|---|
201 e depois completed |
Pago. Dê baixa no título do fornecedor. |
201 com pending_approval: true |
Aguarda aprovação. Não reenvie. |
rejected ou withdrawal.failed |
Não pago. Leia o failure_reason, corrija e envie como pagamento novo, com chave nova. |
400 validation |
Dados inválidos. Corrija e envie com chave nova, porque a mesma chave com outro corpo dá 409. |
409 idempotency_conflict |
A chave foi usada com outro corpo. Revise a geração de chaves no seu código. |
422 transfer_failed |
Recusado, por exemplo por saldo insuficiente ou QR não aceito. Confira o saldo e a mensagem. |
| Sem resposta | Repita com a mesma chave. Se o saque já existir, a API devolve o mesmo. |
A lista completa de códigos está em erros da API Pix. Para fechar o lote com o extrato, use o mesmo raciocínio da conciliação de pagamentos: cada item ligado ao seu transfer_id e ao request_id da chamada.
Controles que valem para todo lote
- Aprovação interna antes do envio. Quem monta o lote não deveria ser quem libera. A API envia o que o seu sistema mandar, então a alçada fica do seu lado.
- Allowlist de IP. Limite as chamadas ao IP do servidor que roda o lote. Fora da lista, a API responde
401. - Segredo fora do código. O
sk_live_fica num cofre ou numa variável de ambiente, nunca no repositório. - Primeiro lote pequeno. Como não há teste de saque no sandbox, valide a fila com a API simulada nos seus testes e rode o primeiro lote real com poucos itens.
A referência dos campos do saque está na documentação da API.
Perguntas frequentes
A API da xFlow tem um endpoint de pagamento em lote?
Não. A documentação descreve um saque por chamada, em POST /api/v1/transfers, com um destino e um valor. Para pagar vários favorecidos, o seu sistema monta uma fila e envia um saque por item.
Como evitar pagar o mesmo fornecedor duas vezes se o lote parar no meio?
Dê a cada item uma Idempotency-Key estável, como o id do lote mais o número do item, e grave o id do saque devolvido. Ao rodar de novo, pule os itens com id gravado e repita os outros com a mesma chave: a API devolve o mesmo saque em vez de debitar o saldo outra vez.
O fornecedor recebe o valor cheio?
Depende do destino. No saque por chave Pix, a taxa sai do próprio amount, e o favorecido recebe o net_amount. No saque por QR Code, o favorecido recebe o valor do QR e a taxa é debitada por cima do seu saldo.
Dá para testar o lote no ambiente de teste?
Não. Com chave de teste, POST /api/v1/transfers responde 403 test_mode_unsupported. Teste a lógica da fila com a API simulada nos seus testes automatizados e comece a produção com um lote pequeno.