Cobrança Pix expirada, na API da xFlow, é a cobrança que passou ao status expired: o prazo acabou e ninguém pagou. A documentação não traz um jeito de reativá-la, e não existe webhook avisando da expiração. O seu sistema descobre consultando a cobrança e, se o cliente ainda quiser pagar, cria uma cobrança nova para o mesmo pedido. Quem cuida disso direito evita duas falhas comuns: mostrar ao cliente um QR Code que já não vale e gerar cobranças em dobro para o mesmo pedido.
O que significa expired
A cobrança nasce pending e muda conforme o pagamento. Estes são os status documentados:
| Status | Significado | O que fazer no pedido |
|---|---|---|
pending |
Aguardando pagamento | Mostrar o QR Code e o copia e cola |
paid |
Pago e confirmado; o líquido entra no saldo | Liberar o pedido |
expired |
Expirou sem pagamento | Oferecer um código novo |
canceled |
Cancelada | Não reaproveitar; criar outra se for o caso |
under_review |
Em análise | Esperar; não liberar o pedido |
blocked |
Bloqueada | Não liberar; verificar com o suporte |
refunded |
Estornada | Tratar como venda desfeita |
A diferença que importa: expired quer dizer que o cliente não pagou a tempo. Não houve erro, recusa nem bloqueio. É o caso mais simples de recuperar.
Quanto tempo a cobrança vale
A documentação da xFlow não traz um parâmetro para definir a validade da cobrança nem informa um prazo padrão. Por isso, não monte um contador regressivo com um prazo inventado. Se a sua tela precisa dizer algo, diga que o código vale por tempo limitado e confira o status antes de agir. A diferença entre QR Code estático e dinâmico, e onde o prazo aparece no padrão do Pix, está em QR Code Pix dinâmico por API.
Como detectar a expiração sem webhook
Como não há evento de expiração, use consulta em dois momentos:
- Quando o cliente volta. Antes de mostrar um QR Code salvo, chame
GET /api/v1/charges/{id}. Se vierexpired, não mostre o código antigo. - Numa varredura periódica. Um job lista as cobranças expiradas do período e atualiza os pedidos que ainda estão como aguardando pagamento.
A listagem aceita status, from e to e pagina por cursor:
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");
async function marcarExpiradas(de, ate) {
let cursor;
do {
const url = new URL(`${BASE}/charges`);
url.searchParams.set("status", "expired");
url.searchParams.set("from", de); // ex.: 2026-10-24
url.searchParams.set("to", ate); // ex.: 2026-10-25
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("starting_after", cursor);
const res = await fetch(url, { headers: { Authorization: AUTH } });
if (!res.ok) throw new Error(`listagem falhou: ${res.status} ${res.headers.get("x-request-id")}`);
const { data, has_more } = await res.json();
for (const cobranca of data) {
await marcarPedidoComoExpirado(cobranca.external_reference, cobranca.id); // a sua regra
}
cursor = has_more ? data[data.length - 1].id : undefined;
} while (cursor);
}Cada página tem de 1 a 100 itens (limit). Enquanto has_more vier true, repita com starting_after igual ao id do último item. A varredura conta no limite de requisições da conta, então rode em intervalos, não em loop contínuo.
Gerar uma cobrança nova para o mesmo pedido
Quando o cliente quer pagar depois da expiração, crie outra cobrança. Dois campos decidem se isso dá certo:
Idempotency-Keynova. Com a chave antiga e o mesmo corpo, a API devolve a cobrança anterior, que está expirada. Com a chave antiga e outro corpo, responde409 idempotency_conflict. Use um sufixo de tentativa, comopedido-1234-2.- O mesmo
external_reference. Ele não precisa ser único. Com o mesmo id do pedido, todas as tentativas aparecem juntas emGET /api/v1/charges?external_reference=pedido-1234.
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-2" \
-d '{
"amountCents": 1990,
"customer": { "name": "Cliente Exemplo", "email": "cliente@exemplo.com", "document": "00000000000" },
"description": "Pedido #1234 (nova tentativa)",
"external_reference": "pedido-1234"
}'No seu banco, guarde a lista de cobranças de cada pedido e marque qual é a vigente. A regra de idempotência completa está em idempotência em API de pagamento.
Pagamento em mais de uma tentativa
Com várias cobranças para o mesmo pedido, o seu handler de transaction.paid precisa achar o pedido pelo external_reference e pelo id da cobrança paga, e não só pela cobrança vigente. Se duas cobranças do mesmo pedido chegarem como pagas, por exemplo porque o cliente pagou uma tentativa e depois a outra, o pedido foi pago duas vezes. Nesse caso, devolva uma delas com POST /api/v1/charges/{id}/refund. O estorno é total, uma vez por cobrança, até 90 dias depois do pagamento. O fluxo está em estorno Pix por API.
Para confirmar qualquer pagamento, a regra é a mesma de sempre: webhook assinado primeiro, consulta como reserva. Veja como saber se o Pix foi pago pela API.
O que mostrar ao cliente
- Mensagem clara. "Este código Pix expirou" e um botão "Gerar novo código".
- Código novo só quando o cliente pede. Não crie cobranças em loop enquanto a página está aberta: cada uma é uma cobrança nova e conta no limite de requisições.
- Código antigo escondido. Tire da tela o QR Code e o copia e cola que expiraram.
- Celular primeiro. Na tela nova, o botão de copiar o código continua sendo o caminho principal para quem paga pelo próprio celular.
Erros comuns
- Confiar no QR salvo. O pedido guardou
pix.copyPasteontem; hoje a cobrança estáexpired. Sempre consulte antes de reexibir. - Reusar a Idempotency-Key. A API devolve a cobrança expirada de novo, e o cliente recebe um código que não vale.
- Esperar um webhook que não existe. Sem varredura, o pedido fica para sempre como aguardando pagamento.
- Buscar o código na listagem. A listagem não traz
pix.copyPastenempix.qrCodeBase64. Para mostrar o código de uma cobrança, useGET /api/v1/charges/{id}.
Os status e os parâmetros da listagem estão na documentação da API. Os eventos que existem estão no guia de webhook Pix.
Perguntas frequentes
Existe webhook de cobrança Pix expirada?
Não. Os eventos de cobrança são transaction.pending, transaction.paid, transaction.refunded e transaction.refund_failed. Para saber se uma cobrança expirou, consulte GET /api/v1/charges/{id} ou liste com GET /api/v1/charges?status=expired.
Posso reativar uma cobrança expirada?
A documentação não traz endpoint para reativar cobrança. Se o cliente ainda quiser pagar, crie uma cobrança nova para o mesmo pedido, com uma Idempotency-Key nova.
Posso usar o mesmo external_reference na cobrança nova?
Sim. O external_reference não precisa ser único. Usar o mesmo id do pedido faz todas as tentativas aparecerem juntas em GET /api/v1/charges?external_reference=.
Como definir o prazo de expiração da cobrança?
A documentação da xFlow não traz parâmetro para definir a validade nem informa um prazo padrão. Trate o status devolvido pela API como a fonte da verdade.