Começar
Testar no sandbox
O que o sandbox é de verdade: Asaas em homologação, Pix real, e onde o loop fecha.
Sandbox não é mock. Suas cobranças saem pelo Infi, um provedor nosso, que por baixo fala com uma conta de homologação do Asaas — o mesmo adapter, os mesmos formatos de webhook, os mesmos modos de falha que produção. O que muda é a conta, não o código.
Na prática: o campo provider volta "infi", e tudo abaixo descreve o
comportamento do Asaas, porque é ele que está por baixo.
Isso é ótimo pra fidelidade, e a parte chata dele acabou: você fecha o loop sozinho, sem a chave de ninguém. O QR que a gente devolve em sandbox é uma página nossa de confirmação — escaneia com o celular, aperta um botão, a fatura vira paga.
O que vem na cobrança pix
{
"provider": "infi",
"method": "pix",
"status": "pending",
"pixPayload": "https://api-sandbox.beinfi.com/pay/{slug}/sandbox/{paymentId}",
"pixQrImage": "iVBORw0KGgoAAAANSUhEUg…",
"sandboxConfirmUrl": "https://api-sandbox.beinfi.com/pay/{slug}/sandbox/{paymentId}",
"providerPixPayload": "00020101021226820014br.gov.bcb.pix2560pix-h.asaas.com/qr/…",
"pixExpiresAt": "2026-08-20T16:30:00Z"
}pixPayload— renderize como QR, igual em produção. Em sandbox ele é a URL da nossa página de confirmação; em produção é o copia-e-cola EMV. Seu código de renderização é o mesmo nos dois: é um QR.pixQrImage— PNG em base64 já pronto, gerado do mesmopixPayload. Se vier, use em vez de gerar o QR você mesmo.sandboxConfirmUrl— só existe em sandbox, e é o marcador explícito. Se você quer um botão "confirmar como se eu tivesse pagado" na sua tela de teste, cheque este campo. Nunca farejando se opixPayloadparece uma URL: em produção ele é EMV, e esse tipo de checagem é como uma coisa de teste vaza pro checkout de verdade.providerPixPayload— o EMV do Asaas, pra você ver o que produção devolveria. Não é pagável em app de banco: é conta de homologação.- Se
pixPayloadvier vazio, a cobrança existe mas não tem como ser paga. Não invente um QR a partir deidouproviderId: o banco do pagador recusa, e a tela mente pra ele. Mostre erro e ofereça outro método.
Pix exige CPF/CNPJ
O Asaas recusa criar o pagador sem CPF/CNPJ — sem ele a cobrança para em
422 customer_tax_id_required. Colete e passe o taxId junto do cliente.
Fechar o loop: confirmar o pagamento
Duas formas, e nenhuma precisa de chave de provedor.
Escaneando, que é o teste que vale — é o fluxo do seu comprador de verdade:
renderize o pixPayload como QR, aponte a câmera do celular, e abra. Cai numa
página nossa com o valor e um botão.
Ou por HTTP, pro seu CI:
curl -X POST "$SANDBOX_CONFIRM_URL"
# -> 200 { "status": "confirmed" }$SANDBOX_CONFIRM_URL é o sandboxConfirmUrl que a cobrança devolveu. No SDK:
const pay = await infi.pay.charge({ slug, invoiceId, method: "pix" });
if (pay.sandboxConfirmUrl) await fetch(pay.sandboxConfirmUrl, { method: "POST" });
await infi.pay.waitForPaid({ slug, invoiceId });Confirmar duas vezes é seguro: a segunda responde 200 e não faz nada — é uma
página que as pessoas recarregam.
Medido de ponta a ponta: confirmação → paid em ~3s, com o grant de download
emitido e a entrega disparada.
Por baixo é o Asaas confirmando, não a gente inventando
A nossa rota não escreve "pago" no banco. Ela chama o confirm de sandbox do
provedor que criou a cobrança, e o webhook real dele volta pelo mesmo caminho que
produção usa. É por isso que você vê pixTransaction, taxa descontada e data de
crédito — a cobrança é real, só a confirmação é sua.
Isso não existe em produção
A rota de confirmação não é montada num deployment live — não é protegida, é
ausente. E o sandboxConfirmUrl não vem na resposta. Se o seu código chama ela,
ele não tem o que chamar em produção; se ele checa o campo, se comporta certo nos
dois. É por isso que o marcador é um campo e não o formato do pixPayload.
Como saber que pagou
Duas formas, e em sandbox só uma funciona hoje:
| sandbox | produção | |
|---|---|---|
| Polling da fatura | ✅ funciona | ✅ funciona |
Webhook payment.confirmed | ❌ 503 ao registrar | ✅ |
Registrar webhook em sandbox responde
503 secret_store_unavailable — o cofre de segredos não está disponível pra
tenant de sandbox. Então, em sandbox, polling é o caminho:
const pago = await infi.pay.waitForPaid({ slug, invoiceId });
// ou, do lado servidor:
const inv = await infi.invoices.get(invoiceId); // status: "open" | "paid"Os eventos, a verificação de assinatura e o que fazer em produção estão em webhooks.
O que NÃO existe em sandbox
Coisas que respondem erro e não são bug seu:
providers.*→404. Conectar PSP (BYOP) é superfície só de produção. Em sandbox quem processa é o Infi — um provedor nosso, e é o que aparece emprovidernas suas cobranças. Ele roda sobre o Asaas por baixo, o que explica os formatos e os erros que você vê, mas a conta é nossa e não há o que conectar.webhooks.create→503, como acima.- Cartão pode vir desabilitado. A resposta pública do link traz
cardEnabled; se forfalse, só Pix e boleto estão disponíveis naquele tenant.