Para saber se um Pix foi pago pela API da xFlow, use o webhook transaction.paid como caminho principal e a consulta GET /api/v1/charges/{id} como reserva. Uma cobrança está paga quando o status é paid. Antes de liberar o pedido, o seu sistema confere três coisas: que o aviso é legítimo (assinatura do webhook ou resposta da própria API), que não é teste (livemode: true) e que o valor bate com o do pedido.
O que conta como pago
Na documentação, paid significa pago e confirmado, e o líquido entra no seu saldo. A xFlow confere o pagamento na adquirente antes de lançar o valor no saldo. Só depois disso a cobrança vira paid e o webhook é enviado.
A cobrança paga traz, além do status:
paidAt: a data do pagamento, em ISO 8601 (UTC);payer: nome, CPF ou CNPJ e banco de quem de fato pagou, informados pelo PSP;pix.endToEnd: o End-to-end ID, o identificador único da liquidação do Pix.
Os outros status também pedem tratamento: pending (aguardando), expired (expirou sem pagamento), canceled, under_review (em análise), blocked e refunded. Só paid libera o pedido.
Caminho 1: webhook transaction.paid
O webhook avisa o seu servidor sem você perguntar. A cada pagamento confirmado, a xFlow envia um POST para a sua URL, com o header x-webhook-signature (HMAC-SHA256 do corpo cru). O handler deve:
- validar a assinatura antes de ler o corpo;
- conferir
data.status === "paid"edata.livemode === true; - comparar
data.amountcom o valor do pedido (no webhook o campo éamount; na API,amountCents); - achar o pedido pelo
data.external_referenceou pelodata.id; - dar baixa uma vez só, porque o mesmo evento pode chegar de novo;
- responder
2xxrápido, para não provocar reenvio.
O código completo, em Node.js e Python, está no guia de webhook Pix.
Caminho 2: consulta do status
A consulta serve quando o webhook ainda não chegou, por exemplo enquanto o cliente está na tela de pagamento, ou quando você quer confirmar um evento antes de liberar algo de valor alto.
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 aguardarPagamento(id, { tentativas = 20, intervaloMs = 5000 } = {}) {
for (let i = 0; i < tentativas; i++) {
const res = await fetch(`${BASE}/charges/${id}`, { headers: { Authorization: AUTH } });
if (res.status === 429) {
await espera(Number(res.headers.get("retry-after") ?? 5) * 1000);
continue;
}
if (!res.ok) throw new Error(`consulta falhou: ${res.status} ${res.headers.get("x-request-id")}`);
const cobranca = await res.json();
if (cobranca.status !== "pending") return cobranca; // paid, expired, under_review...
await espera(intervaloMs);
}
return null; // ainda pendente: o webhook ou a varredura resolvem depois
}Três cuidados com a consulta:
- Ela conta no limite de requisições, que é por conta. Toda resposta autenticada traz
x-ratelimit-remaining; acima do limite, a resposta é429comretry-after. - Pare quando o status sair de
pending. Um laço sem fim por cobrança abandonada consome o limite da conta à toa. - O intervalo é escolha sua. A documentação não define frequência de consulta. Escolha um intervalo que caiba no limite da sua conta.
Caminho 3: varredura pela listagem
Se o seu servidor ficou fora do ar por mais tempo do que os reenvios do webhook cobrem, recupere o estado pela listagem:
curl "https://app.xflowpayments.com/api/v1/charges?status=paid&from=2026-10-25&to=2026-10-25&limit=100" \
-u "pk_live_...:sk_live_..."A resposta traz data e has_more. Enquanto has_more for true, repita com starting_after igual ao id do último item. Rode a varredura em intervalos e dê baixa nos pedidos que ainda estavam pendentes no seu banco. O mesmo raciocínio, aplicado ao extrato, está em conciliação de pagamentos.
Qual caminho usar em cada situação
| Situação | Caminho |
|---|---|
| Baixa automática do pedido | Webhook transaction.paid |
| Cliente esperando na tela de pagamento | Webhook, com consulta da cobrança como reserva |
| Confirmar antes de liberar algo de valor alto | Webhook e, em seguida, GET /api/v1/charges/{id} |
| Servidor ficou fora do ar | Listagem com status=paid e o período |
| Cobrança antiga sem resposta | Consulta: pode ter virado expired |
A tela do cliente
O navegador ou o app do cliente não chama a API da xFlow, porque a chamada usa o segredo da chave. A tela pergunta ao seu servidor, por exemplo em /pedidos/1234/status, e o servidor responde com o que está no seu banco, atualizado pelo webhook. Só quando o pedido continua pendente por mais tempo que o normal o servidor consulta a API. Assim, muitos clientes olhando a tela ao mesmo tempo não viram uma consulta à API cada um.
Se a cobrança expirar enquanto o cliente espera, ofereça um código novo. O passo a passo está em cobrança Pix expirada.
Não confie em comprovante
Imagem de comprovante enviada pelo cliente não prova pagamento: pode ser editada ou ser de outro Pix. Libere o pedido só com paid vindo da API. Se o cliente insiste que pagou, consulte a cobrança e, se precisar, procure pelo external_reference em GET /api/v1/charges?external_reference=pedido-1234, que mostra todas as cobranças do pedido.
Vale também decidir o que fazer quando quem pagou não é quem comprou. O customer é o que você informou; o payer é quem de fato pagou. Em muitos negócios isso é normal, como um familiar que paga a compra. Em outros, pede uma revisão antes de liberar.
Como testar a verificação
Com a chave de teste, crie a cobrança e chame POST /api/v1/test/charges/{id}/pay. A cobrança vira paid e o webhook transaction.paid chega com livemode: false. Confira se o seu código recusa a baixa de pedidos reais com esse evento e se a consulta devolve paid depois da simulação. O roteiro está em API Pix sandbox, e a referência dos campos na documentação da API.
Perguntas frequentes
Webhook ou consulta: qual usar para verificar o pagamento Pix?
Os dois. O webhook transaction.paid é o caminho principal, porque avisa sem você perguntar. A consulta GET /api/v1/charges/{id} é a reserva, para quando o webhook atrasa, e a listagem com status=paid serve para uma varredura periódica.
O comprovante enviado pelo cliente serve como confirmação?
Não. Imagem de comprovante pode ser editada. O pedido só deve ser liberado quando a API informar status paid, pelo webhook assinado ou pela consulta da cobrança.
O que fazer quando a cobrança está under_review?
Esperar. under_review significa que a cobrança está em análise. Não libere o pedido até a cobrança chegar a paid.
O pagador pode ser outra pessoa?
Pode. customer é o que você informou ao criar a cobrança, e payer é quem de fato pagou, com nome, CPF ou CNPJ e banco informados pelo PSP. Defina no seu sistema o que fazer quando os documentos forem diferentes.