Nesta páginamostrarocultar
IntroduçãoLink para esta seção
A API LofyPay é REST, retorna JSON, autentica por chave e tem rate limit por IP e por chave. Esta documentação cobre os endpoints de cobrança, status, webhooks, saque e health check.
Você precisa de uma chave de API gerada no painel para fazer qualquer requisição. As chaves têm prefixo sk_live_ (produção) ou sk_test_ (sandbox).
- JSON puro, sem SDK obrigatório.
- Respostas sempre em
application/json. - Rate limit do gateway: 30 requisições por minuto por IP e 120 por minuto por chave.
- Allowlist de IPs opcional por conta.
AmbientesLink para esta seção
Use sk_test_ para testes e sk_live_ para produção. As duas chaves apontam para a mesma URL base. O roteamento para o sandbox é feito pelo prefixo da chave.
Tráfego real, taxas reais e movimentação de dinheiro de verdade.
- URL base
- https://app.lofypay.com/api/v1
- Prefixo
- sk_live_
Para desenvolvimento e testes de integração. Nenhuma cobrança real é processada.
- URL base
- https://app.lofypay.com/api/v1
- Prefixo
- sk_test_
URL única
sk_test_ sinaliza o ambiente.Simular pagamento no sandbox
No sandbox o PIX nunca é pago de verdade. Para testar o fluxo completo, marque a transação de teste com o endpoint de simulação. Ele atualiza o status e dispara o webhook de teste para a notification_url. Só aceita chaves sk_test_ (chave live retorna 403) e nunca movimenta saldo real.
https://app.lofypay.com/api/v1/sandbox/pay{
"api-key": "sk_test_...",
"idTransaction": "52fc5262-...",
"status": "paid"
}status aceita paid, failed, expired ou pending. Também dá para identificar a transação por external_reference no lugar de idTransaction.
{
"event": "pix.paid",
"environment": "test",
"test": true,
"idTransaction": "52fc5262-...",
"externalReference": "PEDIDO-123456",
"status": "paid"
}O webhook de teste tem formato próprio
X-Lofypay-Environment: test e o payload acima, sem amount e paid_at e com externalReference em camelCase. Use-o para validar conectividade. O contrato de produção é o da seção Webhooks.AutenticaçãoLink para esta seção
A LofyPay aceita a Secret Key (sk_live_ ou sk_test_) em três lugares. Escolha o que for mais cômodo e nunca a exponha em código que roda no navegador.
Ordem de prioridade na leitura:
Authorization: Bearer sk_live_...(recomendado)x-api-key: sk_live_...(alternativa por header)"api-key": "sk_live_..."no body JSON (compatibilidade)
curl -X POST https://app.lofypay.com/api/v1/gateway \
-H "Authorization: Bearer sk_live_...sua_secret_key..." \
-H "Content-Type: application/json" \
-d '{ "amount": 100.50, "method": "pix", "client": { "name": "Pedro", "document": "12345678900" } }'{
"api-key": "sk_live_...sua_secret_key...",
"amount": 100.50
}Nunca exponha a secret key
sk_live_ dá acesso total à sua conta: cobranças, saques e configurações. Mantenha-a apenas em variáveis de ambiente do servidor. Se vazou, regenere imediatamente em /gateway.Mensagens de erro 401
code específico: missing_key, public_key_used (você colou pk_ em vez de sk_), invalid_format, key_not_found ou key_revoked.Gerar PIXLink para esta seção
Cria uma cobrança PIX dinâmica e devolve o código copia e cola para exibir no checkout. Algumas adquirentes também devolvem o QR Code em base64.
https://app.lofypay.com/api/v1/gatewayParâmetros do body
api-keystringobrigatórioSua chave (
sk_live_ousk_test_). Dispensável se você autenticar por header.amountnumberobrigatórioValor da cobrança em reais. Exemplo: 100.50
methodenumopcional"pix"(padrão),"credit_card"ou"boleto". Cartão e boleto têm seções próprias abaixo.clientobjectobrigatórioDados do pagador. O objeto é obrigatório. Campos ausentes recebem valores neutros.
client.namestringopcionalNome completo do pagador. Recomendado. Sem ele a cobrança é registrada como genérica.
client.documentstringopcionalCPF ou CNPJ do pagador, só dígitos. Recomendado para conciliação e defesa de MED.
client.emailstringopcionalE-mail do pagador. Recomendado para conciliação.
external_referencestringopcionalID do pedido no seu sistema (exemplo:
PEDIDO-1050). Volta no webhook.notification_urlurlopcionalURL HTTPS que recebe um
POSTautomático quando o pagamento confirmar.expirationnumberopcionalValidade da cobrança em segundos, de 60 a 86400.
split.percentualnumberopcionalPercentual do valor bruto a destinar para outra conta, de 0 a 100.
split.destinostringopcionaluser_idda conta que recebe o split.metadata.order_idstringopcionalAlternativa a
external_reference.
| Campo | Tipo | Descrição |
|---|---|---|
api-keyobrigatório | string | Sua chave (sk_live_ ou sk_test_). Dispensável se você autenticar por header. |
amountobrigatório | number | Valor da cobrança em reais. Exemplo: 100.50 |
methodopcional | enum | "pix" (padrão), "credit_card" ou "boleto". Cartão e boleto têm seções próprias abaixo. |
clientobrigatório | object | Dados do pagador. O objeto é obrigatório. Campos ausentes recebem valores neutros. |
client.nameopcional | string | Nome completo do pagador. Recomendado. Sem ele a cobrança é registrada como genérica. |
client.documentopcional | string | CPF ou CNPJ do pagador, só dígitos. Recomendado para conciliação e defesa de MED. |
client.emailopcional | string | E-mail do pagador. Recomendado para conciliação. |
external_referenceopcional | string | ID do pedido no seu sistema (exemplo: PEDIDO-1050). Volta no webhook. |
notification_urlopcional | url | URL HTTPS que recebe um POST automático quando o pagamento confirmar. |
expirationopcional | number | Validade da cobrança em segundos, de 60 a 86400. |
split.percentualopcional | number | Percentual do valor bruto a destinar para outra conta, de 0 a 100. |
split.destinoopcional | string | user_id da conta que recebe o split. |
metadata.order_idopcional | string | Alternativa a external_reference. |
Idempotency-Key
Idempotency-Key também vale aqui. Ele é gravado como external_reference, e repetir a chamada com a mesma chave devolve a cobrança original em vez de criar outra — vale para os três métodos. Se você já manda external_reference no body, ele tem prioridade.Exemplo
{
"api-key": "sk_live_...",
"amount": 100.50,
"method": "pix",
"external_reference": "PEDIDO-123456",
"notification_url": "https://seu-site.com/webhook",
"client": {
"name": "Pedro H. Santos",
"document": "12345678900",
"email": "pedro@exemplo.com"
},
"split": {
"percentual": 10,
"destino": "100042"
}
}curl -X POST https://app.lofypay.com/api/v1/gateway \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 100.50,
"method": "pix",
"external_reference": "PEDIDO-123456",
"client": {
"name": "Pedro H. Santos",
"document": "12345678900",
"email": "pedro@exemplo.com"
}
}'const res = await fetch("https://app.lofypay.com/api/v1/gateway", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.LOFYPAY_SECRET}`,
},
body: JSON.stringify({
amount: 100.50,
method: "pix",
external_reference: "PEDIDO-123456",
client: {
name: "Pedro H. Santos",
document: "12345678900",
email: "pedro@exemplo.com",
},
}),
});
const data = await res.json();
// data.idTransaction, data.paymentCode (e data.paymentCodeBase64, quando disponível)Resposta 200
{
"status": "success",
"paymentCode": "00020126580014br.gov.bcb.pix0136123e4567-...",
"idTransaction": "52fc5262-4063-4900-933b-55e6985096a1",
"paymentCodeBase64": "iVBORw0KGgoAAAANSUhEUgAA...",
"environment": "live"
}paymentCodeBase64 é opcional. Depende da adquirente que processa a cobrança e não vem no sandbox. Para cobrir todos os casos, gere o QR Code você mesmo a partir do paymentCode. No sandbox, status vem "OK" em vez de "success".
HTTP 200 não garante sucesso
{ "status": "error", "message": "..." }. Antes de exibir o checkout, valide o status de sucesso e a presença de idTransaction. Não confie só no HTTP status.Rate limit
HTTP 429 com o header Retry-After em segundos.Cobrança no cartãoLink para esta seção
Cobra no cartão de crédito à vista e devolve o resultado da autorização na mesma resposta. Diferente do Pix, aqui a cobrança é decidida na hora: ou a operadora autoriza, ou recusa.
Requer liberação prévia
403 com card_api_not_enabled. Pix e boleto não dependem dessa liberação.https://app.lofypay.com/api/v1/gatewayParâmetros do body
api-keystringobrigatórioSua chave
sk_. Dispensável se autenticar por header.amountnumberobrigatórioValor total em reais.
methodenumobrigatórioUse
"credit_card".cardobjectobrigatórioCartão. É a presença deste objeto que define a cobrança como cartão.
card.tokenstringobrigatórioCartão tokenizado no navegador com o Stripe.js (
pm_...). Veja Como gerar o token. Número, validade e CVV NÃO são aceitos.card.holder_namestringopcionalNome impresso no cartão.
installmentsnumberopcionalSempre
1: o cartão é só à vista no momento. Outro valor responde400cominvalid_installments.remote_ipstringopcionalIP do DISPOSITIVO do pagador, não do seu servidor.
client.namestringobrigatórioNome do titular.
client.emailstringobrigatórioE-mail do titular.
client.documentstringobrigatórioCPF ou CNPJ do titular, só dígitos.
client.phonestringobrigatórioTelefone do titular, 10 ou 11 dígitos.
client.address.zipcodestringobrigatórioCEP do endereço de cobrança do titular, 8 dígitos (com ou sem hífen). Sem ele a chamada responde
400combilling_address_required.client.address.numberstringobrigatórioNúmero do endereço de cobrança do titular. Use
"S/N"quando não houver.client.address.complementstringopcionalComplemento do endereço do titular.
client.address.countrystringopcionalPaís do titular, ISO de 2 letras (
"US","PT"). Fora do Brasil, CPF, telefone e endereço deixam de ser obrigatórios. Se o cartão for brasileiro, eles voltam a ser exigidos.external_referencestringopcionalID do pedido no seu sistema. Volta no webhook.
notification_urlurlopcionalURL HTTPS que recebe o POST quando o pagamento confirmar.
| Campo | Tipo | Descrição |
|---|---|---|
api-keyobrigatório | string | Sua chave sk_. Dispensável se autenticar por header. |
amountobrigatório | number | Valor total em reais. |
methodobrigatório | enum | Use "credit_card". |
cardobrigatório | object | Cartão. É a presença deste objeto que define a cobrança como cartão. |
card.tokenobrigatório | string | Cartão tokenizado no navegador com o Stripe.js (pm_...). Veja Como gerar o token. Número, validade e CVV NÃO são aceitos. |
card.holder_nameopcional | string | Nome impresso no cartão. |
installmentsopcional | number | Sempre 1: o cartão é só à vista no momento. Outro valor responde 400 com invalid_installments. |
remote_ipopcional | string | IP do DISPOSITIVO do pagador, não do seu servidor. |
client.nameobrigatório | string | Nome do titular. |
client.emailobrigatório | string | E-mail do titular. |
client.documentobrigatório | string | CPF ou CNPJ do titular, só dígitos. |
client.phoneobrigatório | string | Telefone do titular, 10 ou 11 dígitos. |
client.address.zipcodeobrigatório | string | CEP do endereço de cobrança do titular, 8 dígitos (com ou sem hífen). Sem ele a chamada responde 400 com billing_address_required. |
client.address.numberobrigatório | string | Número do endereço de cobrança do titular. Use "S/N" quando não houver. |
client.address.complementopcional | string | Complemento do endereço do titular. |
client.address.countryopcional | string | País do titular, ISO de 2 letras ("US", "PT"). Fora do Brasil, CPF, telefone e endereço deixam de ser obrigatórios. Se o cartão for brasileiro, eles voltam a ser exigidos. |
external_referenceopcional | string | ID do pedido no seu sistema. Volta no webhook. |
notification_urlopcional | url | URL HTTPS que recebe o POST quando o pagamento confirmar. |
O número do cartão nunca passa pelo seu servidor
pm_.... Mandar card.number, validade ou CVV responde 400 com card_token_required. Assim nenhum dado de cartão trafega pelo seu sistema nem pelo nosso. Bandeiras aceitas: Visa e Mastercard.Os campos do titular e o endereço são obrigatórios no cartão brasileiro
client.address.zipcode e client.address.number) não são opcionais aqui, ao contrário do Pix e do boleto. A adquirente exige a ficha do titular para a análise de risco e o emissor confere o CEP. Envie o endereço do PORTADOR do cartão, não o da sua empresa: um CEP que não é dele derruba a aprovação. Sem endereço, a chamada responde 400 com billing_address_required e nada é cobrado.Comprador de fora do Brasil
client.address.country com o país do titular, além de nome, e-mail e card.token. CPF, telefone e CEP podem ficar de fora. O país real do cartão é conferido antes da cobrança: cartão emitido no Brasil continua exigindo CPF e endereço.Exemplo de requisição
{
"api-key": "sk_live_...",
"amount": 199.90,
"method": "credit_card",
"installments": 1,
"remote_ip": "189.1.1.1",
"external_reference": "PEDIDO-123456",
"notification_url": "https://sualoja.com/webhooks/lofypay",
"client": {
"name": "Maria Silva",
"email": "maria@exemplo.com",
"document": "12345678909",
"phone": "11987654321",
"address": { "zipcode": "01310100", "number": "1000" }
},
"card": {
"token": "pm_1Q2w3E4r5T6y7U8i9O0p",
"holder_name": "MARIA SILVA"
}
}Como gerar o token
Na sua página de pagamento, carregue o Stripe.js com a chave pública da LofyPay, monte o campo de cartão e, no envio, troque o cartão por um token. Mande só o paymentMethod.id para o seu servidor e dele para a LofyPay em card.token. O token vale para uma cobrança; gere um novo a cada tentativa.
<div id="cartao"></div>
<script src="https://js.stripe.com/v3/"></script>
<script>
// Chave PÚBLICA da LofyPay (pode ficar no front)
const stripe = Stripe("pk_live_51UNF9mGpPZtcjOwG1iVQLYwVpadVC2cEdTVkYxTY50y4UslM7QcYy8um4mCF8bPpeahV1yEaAMfdeQDbWbtPm7PK009WUyIw7Z", { locale: "pt-BR" });
const cartao = stripe.elements().create("card", { hidePostalCode: true });
cartao.mount("#cartao");
async function pagar(comprador) {
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: "card",
card: cartao,
billing_details: {
name: comprador.nome,
email: comprador.email,
address: { postal_code: comprador.cep, country: "BR" },
},
});
if (error) return alert(error.message);
// Envie paymentMethod.id ao SEU servidor, que chama a LofyPay com card.token
await fetch("/seu-backend/pagar", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: paymentMethod.id }),
});
}
</script>Use a chave pública da LofyPay, não uma sua
400 com invalid_card. A chave acima é pública e pode ficar no código do navegador. A chave secreta continua sendo só a sua sk_ da LofyPay.Resposta — autorizado
{
"status": "OK",
"paymentCode": "",
"idTransaction": "pay_3b1jo2ytgip1c1rc",
"cardStatus": "SUCCEEDED",
"message": "Pagamento autorizado.",
"environment": "live"
}paymentCode vem vazio de propósito: no cartão não existe código para o comprador copiar. O identificador da transação é idTransaction.
Resposta — recusado
{
"status": "error",
"code": "card_declined",
"message": "Pagamento não autorizado pela operadora do cartão. Tente outro meio de pagamento."
}A mensagem de recusa é genérica de propósito
Prazo, parcelamento e bandeiras
ParcelamentoindisponívelO cartão é só à vista no momento, na API e no checkout. Envie
installments= 1 ou omita.BandeirasVisa · MastercardElo, Amex e Hipercard são recusados.
Liberação~30 diasA venda aprovada fica em “a receber” e vira saldo disponível quando a processadora libera: cerca de 30 dias no cartão nacional e 5 no internacional.
TaxaúnicaO custo da processadora já está dentro da taxa da sua conta. Os valores estão em Taxas.
| Campo | Tipo | Descrição |
|---|---|---|
Parcelamento | indisponível | O cartão é só à vista no momento, na API e no checkout. Envie installments = 1 ou omita. |
Bandeiras | Visa · Mastercard | Elo, Amex e Hipercard são recusados. |
Liberação | ~30 dias | A venda aprovada fica em “a receber” e vira saldo disponível quando a processadora libera: cerca de 30 dias no cartão nacional e 5 no internacional. |
Taxa | única | O custo da processadora já está dentro da taxa da sua conta. Os valores estão em Taxas. |
Cobrança no boletoLink para esta seção
Emite um boleto bancário e devolve a linha digitável e o PDF. Diferente do cartão, aqui nada é cobrado na hora: o boleto é gerado e o pagamento acontece depois, no banco.
https://app.lofypay.com/api/v1/gatewayParâmetros do body
api-keystringobrigatórioSua chave
sk_.amountnumberobrigatórioValor do boleto em reais.
methodenumobrigatórioUse
"boleto".client.namestringobrigatórioNome do pagador. Vai impresso no boleto.
client.documentstringobrigatórioCPF ou CNPJ do pagador, só dígitos. Vai impresso no boleto.
client.emailstringopcionalE-mail do pagador. Recomendado para conciliação.
client.phonestringopcionalTelefone do pagador.
client.address.zipcodestringopcionalCEP do pagador. Quando informado, compõe o endereço impresso no boleto.
client.address.numberstringopcionalNúmero do endereço do pagador.
external_referencestringopcionalID do pedido no seu sistema. Volta no webhook.
notification_urlurlopcionalURL HTTPS que recebe o POST quando o boleto for pago.
| Campo | Tipo | Descrição |
|---|---|---|
api-keyobrigatório | string | Sua chave sk_. |
amountobrigatório | number | Valor do boleto em reais. |
methodobrigatório | enum | Use "boleto". |
client.nameobrigatório | string | Nome do pagador. Vai impresso no boleto. |
client.documentobrigatório | string | CPF ou CNPJ do pagador, só dígitos. Vai impresso no boleto. |
client.emailopcional | string | E-mail do pagador. Recomendado para conciliação. |
client.phoneopcional | string | Telefone do pagador. |
client.address.zipcodeopcional | string | CEP do pagador. Quando informado, compõe o endereço impresso no boleto. |
client.address.numberopcional | string | Número do endereço do pagador. |
external_referenceopcional | string | ID do pedido no seu sistema. Volta no webhook. |
notification_urlopcional | url | URL HTTPS que recebe o POST quando o boleto for pago. |
Nome e CPF são obrigatórios
Exemplo de requisição
{
"api-key": "sk_live_...",
"amount": 249.90,
"method": "boleto",
"external_reference": "PEDIDO-123456",
"notification_url": "https://sualoja.com/webhooks/lofypay",
"client": {
"name": "Maria Silva",
"document": "12345678909",
"email": "maria@exemplo.com"
}
}Resposta
{
"status": "OK",
"paymentCode": "46191.11000 00000.000042 88016.368016 1 15780000002000",
"idTransaction": "pay_ygek2499x63qfjp1",
"message": "Boleto emitido.",
"data": {
"boleto_url": "https://www.asaas.com/b/pdf/...",
"linha_digitavel": "46191110000000000042880163680161157800000002000",
"codigo_barras": "46199157800000020004611100000000004288016368016",
"vencimento": "2026-09-23"
},
"environment": "live"
}paymentCode repete a linha digitável — é o mesmo campo onde o Pix devolve o copia e cola, para quem já integrou não precisar tratar boleto como caso especial. O número sem formatação fica em data.linha_digitavel; use esse na hora de copiar, e o formatado só para exibir.
Vencimento e confirmação
Vencimento3 diasContados da emissão. Depois disso o boleto não pode mais ser pago e a cobrança fica sem efeito.
Confirmaçãoaté 3 dias úteisO banco leva esse tempo para informar o pagamento. O webhook dispara quando a confirmação chega, não quando o cliente paga.
Pagamento por PixaceitoO PDF do boleto traz um QR Code Pix. Pagar por ele confirma a mesma cobrança e dispara o mesmo webhook.
| Campo | Tipo | Descrição |
|---|---|---|
Vencimento | 3 dias | Contados da emissão. Depois disso o boleto não pode mais ser pago e a cobrança fica sem efeito. |
Confirmação | até 3 dias úteis | O banco leva esse tempo para informar o pagamento. O webhook dispara quando a confirmação chega, não quando o cliente paga. |
Pagamento por Pix | aceito | O PDF do boleto traz um QR Code Pix. Pagar por ele confirma a mesma cobrança e dispara o mesmo webhook. |
Consultar statusLink para esta seção
Verifica se um pagamento foi aprovado usando o idTransaction retornado na criação. Útil para polling, mas prefira webhooks quando possível.
https://app.lofypay.com/api/v1/status{
"idtransaction": "52fc5262-4063-4900-933b-55e6985096a1"
}Resposta
{
"status": "PAID_OUT"
}Valores possíveis: WAITING_FOR_APPROVAL PAID_OUT EXPIRED REFUNDED FAILED. Pagamento confirmado é PAID_OUT.
Transação não encontrada ou body inválido responde HTTP 400 com { "error": "No matching record found" }.
Prefira webhooks
notification_url na criação do PIX ou cadastre um endpoint de webhook e a LofyPay avisa quando o pagamento confirmar, normalmente em menos de 2 segundos.Split de PagamentosLink para esta seção
Divide o valor líquido de uma cobrança PIX entre a sua conta e outras contas LofyPay. O pagador faz um único PIX; a divisão acontece dentro da LofyPay, como movimentação de saldo, sem nenhum PIX extra.
Ativação
O split é liberado por conta pela LofyPay (fale com o suporte). Sem liberação, enviar split responde HTTP 403 com { "code": "split_not_enabled", "message": "Split de pagamentos não habilitado para esta conta." }. Cada recebedor informa o próprio ID de conta (formato usr_...), visível em Gateway / API → Split. A ausência de split (ou "split": []) é um pagamento normal — nada muda para quem não usa.
Criação
Mesmo endpoint e mesmos campos de Gerar PIX, com a lista split:
https://app.lofypay.com/api/v1/gatewaysplitarrayopcionalLista de recebedores. Máximo configurado pela LofyPay (padrão: 10). Só PIX.
split[].accountIdstringobrigatórioID da conta recebedora (
usr_...). Precisa estar ativa, poder receber split e ser do mesmo tipo (White/Black) que a sua. Não pode ser a sua própria conta nem repetir na lista.split[].percentagenumberopcionalPercentual do valor LÍQUIDO, maior que 0 e até 100, com até 2 casas (ex.: 12.5). A soma dos percentuais não passa de 100%.
split[].amountintegeropcionalValor fixo em CENTAVOS, inteiro maior que 0 (R$ 50,00 =
5000). Atenção: oamountda cobrança continua em reais.
| Campo | Tipo | Descrição |
|---|---|---|
splitopcional | array | Lista de recebedores. Máximo configurado pela LofyPay (padrão: 10). Só PIX. |
split[].accountIdobrigatório | string | ID da conta recebedora (usr_...). Precisa estar ativa, poder receber split e ser do mesmo tipo (White/Black) que a sua. Não pode ser a sua própria conta nem repetir na lista. |
split[].percentageopcional | number | Percentual do valor LÍQUIDO, maior que 0 e até 100, com até 2 casas (ex.: 12.5). A soma dos percentuais não passa de 100%. |
split[].amountopcional | integer | Valor fixo em CENTAVOS, inteiro maior que 0 (R$ 50,00 = 5000). Atenção: o amount da cobrança continua em reais. |
percentage OU amount, nunca os dois
{
"api-key": "sk_live_...",
"amount": 1000.00,
"external_reference": "pedido-123",
"client": { "name": "Maria Silva", "document": "12345678909" },
"split": [
{ "accountId": "usr_parceiroa", "percentage": 10 },
{ "accountId": "usr_parceirob", "amount": 5000 }
]
}Resposta
A resposta de sempre, mais o bloco split (valores em centavos):
{
"status": "OK",
"paymentCode": "00020101021226...",
"idTransaction": "52fc5262-4063-4900-933b-55e6985096a1",
"environment": "live",
"split": {
"enabled": true,
"netAmount": 97950,
"merchantAmount": 83155,
"recipients": [
{ "accountId": "usr_parceiroa", "percentage": 10, "amount": 9795 },
{ "accountId": "usr_parceirob", "fixedAmount": 5000, "amount": 5000 }
]
}
}Como o valor é calculado
O split acontece depois das taxas. A taxa da LofyPay sai inteira da cobrança e não é dividida entre os participantes:
bruto R$ 1.000,00
taxa (2% + 0,50) R$ 20,50
líquido R$ 979,50 ← base do split
parceiro A 10% R$ 97,95
parceiro B 5% R$ 48,98 (48,975 arredondado)
sua conta R$ 832,57 (o que sobra)
bruto = taxas + sua conta + splits (sempre, ao centavo)CentavosinteiroTodo cálculo é feito em centavos inteiros, sem ponto flutuante.
Percentualmeio para cimaCada percentual é arredondado ao centavo mais próximo; meio centavo sobe (48,975 → 48,98).
Sobrasua contaDiferença de centavos por arredondamento fica com a conta principal. Em distribuições de 100%, o ajuste de 1 centavo sai do último recebedor percentual.
Valor mínimoR$ 0,01Se um percentual resultar em R$ 0,00 para algum recebedor, a cobrança é recusada (split_amount_too_small).
| Campo | Tipo | Descrição |
|---|---|---|
Centavos | inteiro | Todo cálculo é feito em centavos inteiros, sem ponto flutuante. |
Percentual | meio para cima | Cada percentual é arredondado ao centavo mais próximo; meio centavo sobe (48,975 → 48,98). |
Sobra | sua conta | Diferença de centavos por arredondamento fica com a conta principal. Em distribuições de 100%, o ajuste de 1 centavo sai do último recebedor percentual. |
Valor mínimo | R$ 0,01 | Se um percentual resultar em R$ 0,00 para algum recebedor, a cobrança é recusada (split_amount_too_small). |
Confirmação e imutabilidade
A configuração do split é gravada junto com a cobrança e não muda depois — nem se as contas ou permissões mudarem. O saldo de cada participante só é creditado quando a instituição confirma o pagamento, numa única operação atômica: ou todos recebem, ou ninguém recebe. Webhooks repetidos da instituição não geram crédito duplicado.
Consulta
Em Consultar status com a sua Secret Key, a resposta de uma cobrança com split traz a distribuição. Sem chave (ou com chave de outra conta), só o status aparece.
{
"status": "PAID_OUT",
"split": {
"status": "COMPLETED",
"amount": 100000,
"netAmount": 97950,
"merchantAmount": 83155,
"recipients": [
{ "accountId": "usr_parceiroa", "percentage": 10, "amount": 9795, "status": "COMPLETED" },
{ "accountId": "usr_parceirob", "fixedAmount": 5000, "amount": 5000, "status": "COMPLETED" }
]
}
}PENDINGstatusCobrança criada, aguardando pagamento.
COMPLETEDstatusPagamento confirmado e valores creditados.
REVERSEDstatusPagamento estornado; os créditos foram revertidos.
| Campo | Tipo | Descrição |
|---|---|---|
PENDING | status | Cobrança criada, aguardando pagamento. |
COMPLETED | status | Pagamento confirmado e valores creditados. |
REVERSED | status | Pagamento estornado; os créditos foram revertidos. |
Webhook
Em payment.approved (e no POST para a notification_url) vem o bloco split. Os demais campos não mudam; net_amount continua sendo o valor creditado na sua conta (em reais), ou seja, já descontado o split.
"split": {
"netAmount": 97950,
"merchantAmount": 83155,
"split": [
{ "accountId": "usr_parceiroa", "percentage": 10, "amount": 9795 },
{ "accountId": "usr_parceirob", "fixedAmount": 5000, "amount": 5000 }
]
}Estornos
No estorno integral, cada participante devolve exatamente o que recebeu naquela cobrança (nada é recalculado) e a taxa da LofyPay também é estornada. Os lançamentos originais continuam no extrato e o estorno aparece como lançamento inverso. Se um recebedor já tiver sacado, o saldo dele pode ficar negativo, como em qualquer estorno. Estorno parcial é tratado manualmente pelo suporte. payment.refunded traz split.reversed com os valores devolvidos.
Erros
split_not_enabled403Conta sem liberação de split.
split_disabled403Split temporariamente desativado na plataforma.
split_method_not_supported400Split só em PIX.
split_invalid / split_invalid_account400Formato da lista ou do accountId inválido.
split_account_not_found400accountId não existe.
split_account_cannot_receive400Conta inativa, bloqueada, suspensa ou sem permissão para receber split.
split_self_account400A sua própria conta como recebedora.
split_duplicate_account400A mesma conta mais de uma vez.
split_style_mismatch400Conta recebedora de outro tipo (White/Black).
split_processor_mismatch400Conta recebedora opera em outra processadora. Split só entre contas da mesma processadora.
split_both_percentage_and_amount400percentage e amount no mesmo item.
split_missing_percentage_or_amount400Nem percentage nem amount.
split_invalid_percentage / split_invalid_amount400Zero, negativo, acima de 100%, mais de 2 casas ou amount não inteiro.
split_percentage_over_limit400Soma dos percentuais acima do permitido.
split_exceeds_net400Soma do split maior que o valor líquido da cobrança.
split_amount_too_small400Algum recebedor ficaria com R$ 0,00.
split_too_many_recipients400Mais recebedores que o limite.
split_limit_exceeded400Limite por transação, diário ou mensal de split atingido.
split_conflict_account_split400A conta tem repasse automático configurado pela LofyPay.
split_unavailable / split_error503 / 500Falha temporária ao validar ou registrar o split. Nenhuma cobrança foi criada; tente de novo.
| Campo | Tipo | Descrição |
|---|---|---|
split_not_enabled | 403 | Conta sem liberação de split. |
split_disabled | 403 | Split temporariamente desativado na plataforma. |
split_method_not_supported | 400 | Split só em PIX. |
split_invalid / split_invalid_account | 400 | Formato da lista ou do accountId inválido. |
split_account_not_found | 400 | accountId não existe. |
split_account_cannot_receive | 400 | Conta inativa, bloqueada, suspensa ou sem permissão para receber split. |
split_self_account | 400 | A sua própria conta como recebedora. |
split_duplicate_account | 400 | A mesma conta mais de uma vez. |
split_style_mismatch | 400 | Conta recebedora de outro tipo (White/Black). |
split_processor_mismatch | 400 | Conta recebedora opera em outra processadora. Split só entre contas da mesma processadora. |
split_both_percentage_and_amount | 400 | percentage e amount no mesmo item. |
split_missing_percentage_or_amount | 400 | Nem percentage nem amount. |
split_invalid_percentage / split_invalid_amount | 400 | Zero, negativo, acima de 100%, mais de 2 casas ou amount não inteiro. |
split_percentage_over_limit | 400 | Soma dos percentuais acima do permitido. |
split_exceeds_net | 400 | Soma do split maior que o valor líquido da cobrança. |
split_amount_too_small | 400 | Algum recebedor ficaria com R$ 0,00. |
split_too_many_recipients | 400 | Mais recebedores que o limite. |
split_limit_exceeded | 400 | Limite por transação, diário ou mensal de split atingido. |
split_conflict_account_split | 400 | A conta tem repasse automático configurado pela LofyPay. |
split_unavailable / split_error | 503 / 500 | Falha temporária ao validar ou registrar o split. Nenhuma cobrança foi criada; tente de novo. |
WebhooksLink para esta seção
Dois canais: a notification_url por cobrança (POST simples quando o PIX confirma, sem assinatura) e os endpoints registrados em /gateway (eventos assinados com HMAC).
Canal 1: notification_url por cobrança
Quando você passa notification_url no payload de criação, a LofyPay dispara um POST para o seu servidor assim que o pagamento confirma. Esse canal envia apenas Content-Type: application/json e não é assinado.
{
"status": "PAID",
"idTransaction": "52fc5262-4063-4900-933b-55e6985096a1",
"amount": 100.50,
"external_reference": "PEDIDO-123456",
"paid_at": "2026-05-20 14:30:12"
}Cartão e boleto usam o MESMO canal e o mesmo formato, com dois campos a mais: payment_method (credit_card ou boleto) e net_amount, o líquido creditado ao vendedor.
{
"idTransaction": "pay_3b1jo2ytgip1c1rc",
"status": "PAID",
"amount": 199.90,
"net_amount": 185.92,
"payment_method": "credit_card",
"external_reference": "PEDIDO-123456",
"paid_at": "2026-09-21T14:30:12.000Z"
}Confirmado não é disponível
{
"idTransaction": "pay_3b1jo2ytgip1c1rc",
"status": "REFUNDED",
"amount": 199.90,
"net_amount": 185.92,
"payment_method": "credit_card",
"external_reference": "PEDIDO-123456",
"paid_at": "2026-10-02T09:12:44.000Z"
}Canal sem assinatura, sempre concilie
/api/v1/status com o idTransaction recebido e só libere se voltar PAID_OUT.Confirme o recebimento com HTTP 200
Canal 2: endpoints registrados e eventos
Além da notification_url por cobrança, que sinaliza só o pagamento confirmado, você pode cadastrar endpoints de webhook em /gateway e assinar os eventos que quiser. Todo evento desse canal vai assinado com HMAC-SHA256 no header X-LofyPay-Signature usando o secret do endpoint.
{
"event": "payment.approved",
"sent_at": "2026-06-01T14:30:12.000Z",
"data": { ... }
}payment.pendingeventoCobrança criada e aguardando o pagador. Traz
expires_atcom a validade do QR Code.payment.approvedeventoCobrança confirmada, em qualquer método. Leia
payment_methodpara saber qual.payment.refundedeventoCobrança estornada ou devolvida. O crédito foi revertido.
payment.cancelledeventoCobrança expirou sem pagamento. Campo
reasoncom valorexpired.saque.requestedeventoSaque criado via API.
statusvemPROCESSINGouPENDING_REVIEW.saque.paideventoA adquirente confirmou a liquidação do PIX. É o evento que fecha o saque: o
POSTdevolvePROCESSINGe só aqui vira pago. Trazrecipientcom quem recebeu: nome, documento mascarado e banco, como no comprovante.saque.failedeventoSaque recusado ou falhou depois de aceito. O saldo já foi devolvido à conta. Campo
reasontraz o motivo.med.openedeventoAbriu um MED (infração PIX ou devolução pelo Banco Central) sobre uma transação sua.
med.updatedeventoMED mudou de status, por exemplo em análise ou defesa enviada.
med.resolvedeventoMED encerrado. Campo
resolution:lost(procedente) oukept(improcedente).
| Campo | Tipo | Descrição |
|---|---|---|
payment.pending | evento | Cobrança criada e aguardando o pagador. Traz expires_at com a validade do QR Code. |
payment.approved | evento | Cobrança confirmada, em qualquer método. Leia payment_method para saber qual. |
payment.refunded | evento | Cobrança estornada ou devolvida. O crédito foi revertido. |
payment.cancelled | evento | Cobrança expirou sem pagamento. Campo reason com valor expired. |
saque.requested | evento | Saque criado via API. status vem PROCESSING ou PENDING_REVIEW. |
saque.paid | evento | A adquirente confirmou a liquidação do PIX. É o evento que fecha o saque: o POST devolve PROCESSING e só aqui vira pago. Traz recipient com quem recebeu: nome, documento mascarado e banco, como no comprovante. |
saque.failed | evento | Saque recusado ou falhou depois de aceito. O saldo já foi devolvido à conta. Campo reason traz o motivo. |
med.opened | evento | Abriu um MED (infração PIX ou devolução pelo Banco Central) sobre uma transação sua. |
med.updated | evento | MED mudou de status, por exemplo em análise ou defesa enviada. |
med.resolved | evento | MED encerrado. Campo resolution: lost (procedente) ou kept (improcedente). |
Headers enviados no canal registrado
Content-Typestringapplication/jsonX-LofyPay-EventstringTipo do evento, por exemplo
payment.approved.X-LofyPay-TimestampstringUnix em segundos de quando o evento foi assinado.
X-LofyPay-SignaturestringAssinatura no formato
t=<unix>,v1=<hmac>. O HMAC-SHA256 é calculado sobre<timestamp>.<body>com o secret do endpoint.X-LofyPay-IdstringUUID único da entrega. Use para deduplicar.
User-AgentstringLofyPay-Webhook/1.0
| Campo | Tipo | Descrição |
|---|---|---|
Content-Type | string | application/json |
X-LofyPay-Event | string | Tipo do evento, por exemplo payment.approved. |
X-LofyPay-Timestamp | string | Unix em segundos de quando o evento foi assinado. |
X-LofyPay-Signature | string | Assinatura no formato t=<unix>,v1=<hmac>. O HMAC-SHA256 é calculado sobre <timestamp>.<body> com o secret do endpoint. |
X-LofyPay-Id | string | UUID único da entrega. Use para deduplicar. |
User-Agent | string | LofyPay-Webhook/1.0 |
Não aceite payment.approved sem verificar
POST no seu endpoint. Só trate o evento depois de validar a X-LofyPay-Signature: apenas a LofyPay conhece o secret do seu endpoint, então uma assinatura válida é a prova de origem. Rejeite também timestamps fora da janela.Onde achar o secret do endpoint
sk_live_: a Secret Key autentica você na API da LofyPay e o secret do endpoint serve para você conferir que um webhook veio da LofyPay.Validando a assinatura
import crypto from "node:crypto";
const WEBHOOK_SECRET = process.env.LOFYPAY_WEBHOOK_SECRET; // secret do endpoint (/gateway > Webhooks)
function verifyLofypay(rawBody, signatureHeader, toleranceSec = 300) {
if (!signatureHeader) return false;
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
const ts = Number(parts.t);
const given = parts.v1;
if (!ts || !given) return false;
if (Math.abs(Date.now() / 1000 - ts) > toleranceSec) return false; // anti-replay
const expected = crypto.createHmac("sha256", WEBHOOK_SECRET).update(`${ts}.${rawBody}`).digest("hex");
return expected.length === given.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given));
}
// use o corpo bruto, sem parse, para o HMAC bater
app.post("/webhooks/lofypay", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
if (!verifyLofypay(raw, req.get("X-LofyPay-Signature"))) return res.sendStatus(401);
const { event, data } = JSON.parse(raw);
const deliveryId = req.get("X-LofyPay-Id"); // guarde para deduplicar reentregas
if (event === "payment.approved") {
// libere o pedido usando data.idTransaction / data.external_reference
}
res.sendStatus(200); // responda rápido, processe depois
});Retentativas e reentregas
X-LofyPay-Id e o mesmo corpo. Faça seu processamento idempotente.Testando antes de ir para produção
{
"event": "med.opened",
"sent_at": "2026-06-01T14:30:12.000Z",
"data": {
"id": 1024,
"idTransaction": "52fc5262-4063-4900-933b-55e6985096a1",
"external_reference": "PEDIDO-123456",
"amount": 100.50,
"status": "open",
"type": "med",
"reason": "Fraude alegada pelo pagador",
"opened_at": "2026-06-01T14:30:00.000Z"
}
}Por que assinamos o timestamp junto com o corpo
<timestamp>.<body> em vez de só o corpo impede que alguém capture um webhook válido e o reenvie dias depois. Rejeite eventos cujo timestamp esteja a mais de 5 minutos do relógio do seu servidor.Cashout e saqueLink para esta seção
Envie PIX para qualquer chave a partir do seu saldo disponível e acompanhe o status até a liquidação.
Criar saque
https://app.lofypay.com/api/v1/cashoutO caminho /api/c1/cashout continua aceito como alias para integrações antigas. Use /api/v1/cashout em novos projetos.
api-keystringobrigatórioSua Secret Key
sk_live_ousk_test_. Também pode ir no headerAuthorization: Bearer.amountnumberobrigatórioValor do saque em reais. Mínimo R$ 1,00 (na rota CyberHub o mínimo é R$ 15,00). Máximo é o seu saldo disponível.
namestringobrigatórioNome completo do favorecido.
cpfstringobrigatórioCPF ou CNPJ do favorecido, com ou sem pontuação.
keypixstringobrigatórioChave PIX de destino.
tipo_chavestringobrigatórioTipo da chave:
CPF,CNPJ,EMAIL,TELEFONEouEVP(chave aleatória).Idempotency-KeyheaderopcionalHeader opcional. Repetir a chamada com a mesma chave devolve o mesmo saque em vez de criar outro. Recomendado para retentativas.
| Campo | Tipo | Descrição |
|---|---|---|
api-keyobrigatório | string | Sua Secret Key sk_live_ ou sk_test_. Também pode ir no header Authorization: Bearer. |
amountobrigatório | number | Valor do saque em reais. Mínimo R$ 1,00 (na rota CyberHub o mínimo é R$ 15,00). Máximo é o seu saldo disponível. |
nameobrigatório | string | Nome completo do favorecido. |
cpfobrigatório | string | CPF ou CNPJ do favorecido, com ou sem pontuação. |
keypixobrigatório | string | Chave PIX de destino. |
tipo_chaveobrigatório | string | Tipo da chave: CPF, CNPJ, EMAIL, TELEFONE ou EVP (chave aleatória). |
Idempotency-Keyopcional | header | Header opcional. Repetir a chamada com a mesma chave devolve o mesmo saque em vez de criar outro. Recomendado para retentativas. |
{
"api-key": "sk_live_...",
"amount": 250.00,
"name": "Maria da Silva",
"cpf": "123.456.789-09",
"keypix": "maria@exemplo.com",
"tipo_chave": "EMAIL"
}curl -X POST https://app.lofypay.com/api/v1/cashout \
-H "Content-Type: application/json" \
-H "Idempotency-Key: saque-0001" \
-d '{
"api-key": "sk_live_...",
"amount": 250.00,
"name": "Maria da Silva",
"cpf": "12345678909",
"keypix": "maria@exemplo.com",
"tipo_chave": "EMAIL"
}'{
"success": true,
"code": "SUCCESS",
"message": "Saque em processamento",
"retryable": false,
"status": "PROCESSING",
"withdrawalId": 4321,
"id": 4321
}{
"success": false,
"code": "INSUFFICIENT_BALANCE",
"message": "Saldo insuficiente para este saque",
"retryable": false
}Códigos possíveis: SUCCESS (200), CASHOUT_PENDING_REVIEW (202, aguardando análise), MISSING_FIELDS e INVALID_AMOUNT (400), CASHOUT_IN_PROGRESS (409, já existe um saque em andamento), INSUFFICIENT_BALANCE e PROVIDER_REJECTED (422), CASHOUT_COOLDOWN (429, aguarde antes de tentar de novo) e PROVIDER_NOT_AVAILABLE (502). O campo retryable indica se vale repetir a chamada.
PROCESSING não é pago
POST só confirma que o saque foi aceito e enviado à adquirente. A liquidação chega pelo evento saque.paid (ou saque.failed, com o saldo devolvido). Se preferir consultar, use o GET abaixo até o campo final ficar true.Consultar saque
https://app.lofypay.com/api/v1/cashoutEnvie a Secret Key no header Authorization: Bearer (ou api-key) e um dos filtros abaixo na query string. Só saques da sua própria conta são retornados; qualquer outro id devolve 404.
withdrawalIdnumberopcionalId devolvido no
POST.idempotencyKeystringopcionalA
Idempotency-Keyusada na criação. Também aceita comoexternalReference.transactionIdstringopcionalIdentificador da transação na adquirente, quando disponível.
| Campo | Tipo | Descrição |
|---|---|---|
withdrawalIdopcional | number | Id devolvido no POST. |
idempotencyKeyopcional | string | A Idempotency-Key usada na criação. Também aceita como externalReference. |
transactionIdopcional | string | Identificador da transação na adquirente, quando disponível. |
curl "https://app.lofypay.com/api/v1/cashout?withdrawalId=4321" \
-H "Authorization: Bearer sk_live_..."{
"success": true,
"withdrawalId": 4321,
"status": "COMPLETED",
"final": true,
"amount": 250.00,
"net_amount": 247.50,
"fee": 2.50,
"pix_key_type": "EMAIL",
"idempotencyKey": "saque-0001",
"transactionId": "E1234567820260601143012ABCDEF01",
"acquirer": "onlyup",
"created_at": "2026-06-01T14:28:00.000Z",
"updated_at": "2026-06-01T14:30:12.000Z",
"recipient": {
"name": "MARIA DA SILVA",
"document": "***.456.789-**",
"document_type": "CPF",
"bank": "Nubank",
"ispb": "18236120"
}
}Status possíveis: PENDING_REVIEW (em análise), PROCESSING (enviado à adquirente), COMPLETED (pago), REJECTED (recusado, saldo devolvido) e PROVIDER_UNKNOWN (a adquirente não respondeu; a conciliação automática resolve em alguns minutos). Só COMPLETED e REJECTED vêm com final: true.
recipient é quem recebeu o PIX, informado pela adquirente na liquidação: nome, CPF ou CNPJ mascarado (como no comprovante bancário) e banco. Vale para saque por chave e para Pix copia e cola. Fica null enquanto o saque não foi liquidado. O mesmo objeto vem no webhook saque.paid.
Limites de requisição
Retry-After. Prefira os webhooks a consultar em loop.Pagar Pix copia e colaLink para esta seção
Pague um Pix copia e cola (ou o conteúdo de um QR Code Pix) com o saldo da conta.
https://app.lofypay.com/api/v1/cashout/qrcodeSó para contas white
403 com API_CASHOUT_DISABLED; use o saque por chave.Mesma autenticação, Idempotency-Key, limites de requisição e análise de valor do saque, e o mesmo requisito: o saque pela API precisa estar liberado para a conta. O código é conferido antes de qualquer débito (CRC, valor e, no código de cobrança, se ela ainda pode ser paga).
api-keystringobrigatórioSua Secret Key
sk_live_. Também pode ir no headerAuthorization: Bearer.brcodestringobrigatórioO Pix copia e cola inteiro, começando por
000201. Quebras de linha do copiar e colar são ignoradas.amountnumberopcionalSó para código sem valor definido. Se o código tem valor, é ele que vale: omita o campo ou envie exatamente o mesmo valor; um valor diferente é recusado com
INVALID_AMOUNT.externalReferencestringopcionalSua referência. Sem o header
Idempotency-Key, ela é usada como chave de idempotência.Idempotency-KeyheaderopcionalRepetir a chamada com a mesma chave devolve o mesmo pagamento em vez de pagar de novo. Sem ela, o mesmo código enviado duas vezes em poucos minutos também devolve o pagamento que já existe.
| Campo | Tipo | Descrição |
|---|---|---|
api-keyobrigatório | string | Sua Secret Key sk_live_. Também pode ir no header Authorization: Bearer. |
brcodeobrigatório | string | O Pix copia e cola inteiro, começando por 000201. Quebras de linha do copiar e colar são ignoradas. |
amountopcional | number | Só para código sem valor definido. Se o código tem valor, é ele que vale: omita o campo ou envie exatamente o mesmo valor; um valor diferente é recusado com INVALID_AMOUNT. |
externalReferenceopcional | string | Sua referência. Sem o header Idempotency-Key, ela é usada como chave de idempotência. |
Idempotency-Keyopcional | header | Repetir a chamada com a mesma chave devolve o mesmo pagamento em vez de pagar de novo. Sem ela, o mesmo código enviado duas vezes em poucos minutos também devolve o pagamento que já existe. |
curl -X POST https://app.lofypay.com/api/v1/cashout/qrcode \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: boleto-agua-0925" \
-d '{
"brcode": "00020101021226900014br.gov.bcb.pix2568pix.exemplo.com/qr/v2/cobv/9d36b84f...6304A1B2"
}'{
"success": true,
"code": "SUCCESS",
"message": "Pagamento enviado à adquirente, aguardando confirmação (PROCESSING).",
"retryable": false,
"withdrawalId": "4388",
"status": "PROCESSING",
"transactionId": "wd_mfz3k2.8h1c0q7p"
}Taxa por fora
No saque por chave a taxa sai de dentro do valor. Aqui é o contrário: o recebedor precisa receber o valor exato do código, então a taxa de saque é cobrada à parte. Um código de R$ 100,00 com taxa de R$ 1,50 chega como R$ 100,00 ao recebedor e debita R$ 101,50 do seu saldo. Se o pagamento falhar, volta tudo, taxa incluída.
INVALID_BRCODE400O texto não é um Pix copia e cola válido, está incompleto ou foi alterado (CRC não confere).
INVALID_AMOUNT400O amount enviado é diferente do valor do código, ou o valor é inválido.
MISSING_FIELDS400Faltou o brcode, ou o código não tem valor e o amount não veio.
API_CASHOUT_DISABLED403Conta black, saque via API não liberado para a conta, conta suspensa ou verificação pendente.
BRCODE_NOT_PAYABLE422Código de cobrança já paga, cancelada, vencida ou que não pôde ser consultada.
INSUFFICIENT_BALANCE422O saldo não cobre o valor do código mais a taxa.
| Campo | Tipo | Descrição |
|---|---|---|
INVALID_BRCODE | 400 | O texto não é um Pix copia e cola válido, está incompleto ou foi alterado (CRC não confere). |
INVALID_AMOUNT | 400 | O amount enviado é diferente do valor do código, ou o valor é inválido. |
MISSING_FIELDS | 400 | Faltou o brcode, ou o código não tem valor e o amount não veio. |
API_CASHOUT_DISABLED | 403 | Conta black, saque via API não liberado para a conta, conta suspensa ou verificação pendente. |
BRCODE_NOT_PAYABLE | 422 | Código de cobrança já paga, cancelada, vencida ou que não pôde ser consultada. |
INSUFFICIENT_BALANCE | 422 | O saldo não cobre o valor do código mais a taxa. |
Os demais códigos são os mesmos do saque: CASHOUT_PENDING_REVIEW (202), CASHOUT_IN_PROGRESS (409), CASHOUT_COOLDOWN (429), PROVIDER_REJECTED (422, saldo devolvido) e PROVIDER_NOT_AVAILABLE (502).
Acompanhar
O pagamento é um saque como os outros: acompanhe pelos eventos saque.paid e saque.failed ou pelo GET /api/v1/cashout?withdrawalId=. Na consulta, amount e net_amount são o valor do código, fee é a taxa debitada à parte e pix_key_type vem como QRCODE.
Saque em criptoLink para esta seção
Saque o saldo em USDT na rede BNB Smart Chain (BEP-20), direto para uma carteira.
Só para contas white
403 com API_CASHOUT_DISABLED.Você informa o valor em reais e a carteira; a LofyPay converte e entrega USDT. Mesma autenticação, Idempotency-Key, limites de requisição, intervalo entre saques e análise de valor do saque por chave. A cotação não é travada: o USDT final é calculado no câmbio do momento da conversão e pode diferir um pouco da estimativa.
Cotar
https://app.lofypay.com/api/v1/cashout/cripto?amount=500Mostra a taxa, o total que sai do saldo e o USDT estimado para um valor, sem criar nada. Use antes de sacar para exibir o valor ao seu cliente ou validar os limites.
curl "https://app.lofypay.com/api/v1/cashout/cripto?amount=500" \
-H "Authorization: Bearer sk_live_..."{
"success": true,
"amount": 500.00,
"fee": 30.00,
"total_debited": 530.00,
"pix_sent": 513.00,
"rate_brl_per_usd": 5.62,
"usdt_estimated": 90.28,
"min_amount": 16.67,
"max_amount": 4899.01,
"network": "bsc",
"token": "USDT"
}amountnumberValor em reais que será convertido em USDT.
feenumberTaxa total do saque em cripto, em reais. Cobrada por fora.
total_debitednumberO que sai do seu saldo:
amount+fee.usdt_estimatednumberUSDT estimado a receber, já descontadas as taxas. Arredondado para baixo; não é garantia.
rate_brl_per_usdnumberCotação indicativa em reais por dólar, com spread incluído.
min_amount / max_amountnumberFaixa aceita para
amountno momento. Fora dela o saque respondeINVALID_AMOUNT.
| Campo | Tipo | Descrição |
|---|---|---|
amount | number | Valor em reais que será convertido em USDT. |
fee | number | Taxa total do saque em cripto, em reais. Cobrada por fora. |
total_debited | number | O que sai do seu saldo: amount + fee. |
usdt_estimated | number | USDT estimado a receber, já descontadas as taxas. Arredondado para baixo; não é garantia. |
rate_brl_per_usd | number | Cotação indicativa em reais por dólar, com spread incluído. |
min_amount / max_amount | number | Faixa aceita para amount no momento. Fora dela o saque responde INVALID_AMOUNT. |
Sacar
https://app.lofypay.com/api/v1/cashout/criptoapi-keystringobrigatórioSua Secret Key
sk_live_. Também pode ir no headerAuthorization: Bearer.amountnumberobrigatórioValor em reais a converter em USDT. A taxa é cobrada à parte: sai do saldo
amount+ taxa.walletstringobrigatórioCarteira de destino na BNB Smart Chain (BEP-20):
0x+ 40 caracteres hexadecimais. Endereço de contrato de token e o endereço zero são recusados.externalReferencestringopcionalSua referência. Sem o header
Idempotency-Key, ela é usada como chave de idempotência.Idempotency-KeyheaderopcionalRepetir a chamada com a mesma chave devolve o mesmo saque em vez de criar outro. Recomendado para retentativas.
| Campo | Tipo | Descrição |
|---|---|---|
api-keyobrigatório | string | Sua Secret Key sk_live_. Também pode ir no header Authorization: Bearer. |
amountobrigatório | number | Valor em reais a converter em USDT. A taxa é cobrada à parte: sai do saldo amount + taxa. |
walletobrigatório | string | Carteira de destino na BNB Smart Chain (BEP-20): 0x + 40 caracteres hexadecimais. Endereço de contrato de token e o endereço zero são recusados. |
externalReferenceopcional | string | Sua referência. Sem o header Idempotency-Key, ela é usada como chave de idempotência. |
Idempotency-Keyopcional | header | Repetir a chamada com a mesma chave devolve o mesmo saque em vez de criar outro. Recomendado para retentativas. |
Confira a rede e a carteira
curl -X POST https://app.lofypay.com/api/v1/cashout/cripto \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: usdt-0001" \
-d '{
"amount": 500.00,
"wallet": "0x8f3a1b2c4d5e6f708192a3b4c5d6e7f8091a2b3c"
}'{
"success": true,
"code": "SUCCESS",
"message": "PIX enviado à PixSwap. O USDT chega na carteira em alguns minutos (PROCESSING).",
"retryable": false,
"withdrawalId": "4410",
"status": "PROCESSING",
"transactionId": "wd_mfz9q1.k2j8d0x4"
}INVALID_WALLET400Carteira ausente do formato BEP-20, endereço de contrato do token ou endereço zero.
INVALID_AMOUNT400Valor abaixo do mínimo de saque, fora da faixa aceita no momento, ou baixo demais para sobrar USDT depois das taxas.
API_CASHOUT_DISABLED403Conta black, saque via API não liberado, chave de teste, conta suspensa ou verificação pendente.
INSUFFICIENT_BALANCE422O saldo não cobre o valor mais a taxa.
CRYPTO_LIQUIDITY422Não há USDT suficiente para esse valor agora. Tente um valor menor ou aguarde alguns minutos (
retryable: true).PROVIDER_NOT_AVAILABLE502Saque em cripto temporariamente indisponível. Tente de novo em instantes.
| Campo | Tipo | Descrição |
|---|---|---|
INVALID_WALLET | 400 | Carteira ausente do formato BEP-20, endereço de contrato do token ou endereço zero. |
INVALID_AMOUNT | 400 | Valor abaixo do mínimo de saque, fora da faixa aceita no momento, ou baixo demais para sobrar USDT depois das taxas. |
API_CASHOUT_DISABLED | 403 | Conta black, saque via API não liberado, chave de teste, conta suspensa ou verificação pendente. |
INSUFFICIENT_BALANCE | 422 | O saldo não cobre o valor mais a taxa. |
CRYPTO_LIQUIDITY | 422 | Não há USDT suficiente para esse valor agora. Tente um valor menor ou aguarde alguns minutos (retryable: true). |
PROVIDER_NOT_AVAILABLE | 502 | Saque em cripto temporariamente indisponível. Tente de novo em instantes. |
Os demais códigos são os mesmos do saque: CASHOUT_PENDING_REVIEW (202), CASHOUT_IN_PROGRESS (409), CASHOUT_COOLDOWN (429), PROVIDER_REJECTED (422, saldo devolvido) e PROVIDER_UNKNOWN (200, em análise, não repita).
Acompanhar
O saque só fica COMPLETED quando o USDT chega na carteira, normalmente em poucos minutos. Até lá ele segue em PROCESSING. Acompanhe pelos eventos saque.paid e saque.failed ou pelo GET /api/v1/cashout?withdrawalId=. Nos dois, pix_key_type vem como USDT_BEP20, amount e net_amount são o valor convertido, fee é a taxa debitada à parte e o objeto crypto traz a entrega:
"crypto": {
"network": "bsc",
"token": "USDT",
"wallet": "0x8f3a1b2c4d5e6f708192a3b4c5d6e7f8091a2b3c",
"usdt_estimated": 90.28,
"usdt_received": 90.31,
"tx_hash": "0x5c1e...9a7f"
}usdt_received e tx_hash ficam null até a entrega; use o tx_hash para conferir a transação no BscScan. Se a conversão falhar depois do envio, o saque vai para PROVIDER_UNKNOWN e a equipe resolve manualmente: não crie outro saque para o mesmo valor enquanto isso.
Consultar saldoLink para esta seção
Os mesmos números do painel: o que pode ser sacado agora, o que está retido e o que ainda vai ser liberado.
https://app.lofypay.com/api/v1/balanceEnvie a Secret Key no header Authorization: Bearer (ou api-key). Não há parâmetros: a resposta é sempre da conta dona da chave. A lista de IPs autorizados da chave também vale aqui.
curl "https://app.lofypay.com/api/v1/balance" \
-H "Authorization: Bearer sk_live_..."{
"success": true,
"environment": "live",
"currency": "BRL",
"available": 1847.35,
"blocked": 250.00,
"blocked_med": 250.00,
"receivable": 4210.90,
"next_release_at": "2026-10-02T03:00:00.000Z",
"as_of": "2026-09-25T18:42:10.512Z"
}availablenumberSaldo disponível: o que um
POST /api/v1/cashoutpode sacar agora, antes da taxa de saque. Fica negativo quando um MED retém mais do que havia na conta; as próximas vendas abatem.blockednumberTotal retido que não pode ser sacado: MED em aberto mais retenções feitas pela equipe da LofyPay.
blocked_mednumberA parte de blocked que está presa em MED. Volta para available se o MED for improcedente.
receivablenumberA receber: vendas no cartão e no boleto já pagas que a adquirente ainda não liberou. Ver Liquidação e saldo.
next_release_atstring | nullData prevista da próxima liberação de valores a receber. null quando não há nada a receber.
as_ofstringMomento da consulta (ISO 8601). O saldo muda a cada venda, saque ou MED.
| Campo | Tipo | Descrição |
|---|---|---|
available | number | Saldo disponível: o que um POST /api/v1/cashout pode sacar agora, antes da taxa de saque. Fica negativo quando um MED retém mais do que havia na conta; as próximas vendas abatem. |
blocked | number | Total retido que não pode ser sacado: MED em aberto mais retenções feitas pela equipe da LofyPay. |
blocked_med | number | A parte de blocked que está presa em MED. Volta para available se o MED for improcedente. |
receivable | number | A receber: vendas no cartão e no boleto já pagas que a adquirente ainda não liberou. Ver Liquidação e saldo. |
next_release_at | string | null | Data prevista da próxima liberação de valores a receber. null quando não há nada a receber. |
as_of | string | Momento da consulta (ISO 8601). O saldo muda a cada venda, saque ou MED. |
Com chave de teste (sk_test_) a resposta vem com environment: "test" e todos os valores zerados: a chave de sandbox não lê o saldo real da conta.
Limites de requisição
Health checkLink para esta seção
Verifique se a API está no ar antes de abrir um chamado ou pausar o seu checkout.
https://app.lofypay.com/api/health{ "ok": true }Se o banco de dados estiver indisponível a resposta é 503 com { "ok": false }. O endpoint não exige autenticação e não conta nos limites de requisição.
Liquidação e saldoLink para esta seção
Pagamento aprovado e dinheiro disponível são duas coisas diferentes. No Pix elas acontecem juntas; no cartão e no boleto, não.
Três estados distintos, e confundi-los é a principal fonte de dúvida de quem integra cartão pela primeira vez:
AprovadostatusA operadora autorizou a compra. A venda entra como
PAID_OUTe aparece como paga no painel. Nada de saldo ainda.LiquidadosettlementA adquirente liberou o valor na conta da LofyPay. É o evento que move o dinheiro, e só ele.
DisponívelsaldoO valor entrou no saldo sacável do vendedor. Acontece junto com a liquidação.
| Campo | Tipo | Descrição |
|---|---|---|
Aprovado | status | A operadora autorizou a compra. A venda entra como PAID_OUT e aparece como paga no painel. Nada de saldo ainda. |
Liquidado | settlement | A adquirente liberou o valor na conta da LofyPay. É o evento que move o dinheiro, e só ele. |
Disponível | saldo | O valor entrou no saldo sacável do vendedor. Acontece junto com a liquidação. |
Venda paga não significa saldo disponível
Prazos por método
PiximediatoAprovação e disponibilidade acontecem no mesmo evento. Não existe fase intermediária.
Cartão de crédito~30 diasPrazo da processadora, contado do pagamento (5 dias para cartão internacional). A data prevista de cada venda é gravada individualmente e é ela que vale — o número acima é a referência.
Boleto1 dia útilContado da confirmação do pagamento pelo banco, que por si só leva até 3 dias úteis.
| Campo | Tipo | Descrição |
|---|---|---|
Pix | imediato | Aprovação e disponibilidade acontecem no mesmo evento. Não existe fase intermediária. |
Cartão de crédito | ~30 dias | Prazo da processadora, contado do pagamento (5 dias para cartão internacional). A data prevista de cada venda é gravada individualmente e é ela que vale — o número acima é a referência. |
Boleto | 1 dia útil | Contado da confirmação do pagamento pelo banco, que por si só leva até 3 dias úteis. |
Como acompanhar
Em Financeiro, o cartão Saldo disponível mostra o que pode ser sacado agora e A receber mostra o que ainda não liquidou, com a data da próxima liberação. Pelo webhook, o evento payment.approvedindica aprovação — não disponibilidade. Pela API, Consultar saldo devolve os mesmos valores de disponível e a receber.
Estados possíveis de uma cobrança
WAITING_FOR_APPROVALstatusCobrança criada, aguardando pagamento. É o estado inicial de Pix e boleto, e do cartão em análise de risco.
PAID_OUTstatusPagamento confirmado.
CANCELLEDstatusRecusada pela análise de risco, captura negada ou cobrança removida.
PENDINGliquidaçãoPago, aguardando a adquirente liberar. Só cartão e boleto passam por aqui.
AVAILABLEliquidaçãoLiberado e somado ao saldo disponível.
REVERSEDliquidaçãoEstornado ou perdido em chargeback.
| Campo | Tipo | Descrição |
|---|---|---|
WAITING_FOR_APPROVAL | status | Cobrança criada, aguardando pagamento. É o estado inicial de Pix e boleto, e do cartão em análise de risco. |
PAID_OUT | status | Pagamento confirmado. |
CANCELLED | status | Recusada pela análise de risco, captura negada ou cobrança removida. |
PENDING | liquidação | Pago, aguardando a adquirente liberar. Só cartão e boleto passam por aqui. |
AVAILABLE | liquidação | Liberado e somado ao saldo disponível. |
REVERSED | liquidação | Estornado ou perdido em chargeback. |
Requisição assinadaLink para esta seção
Assinatura HMAC com nonce, para impedir que uma requisição capturada na rede seja reenviada por outra pessoa.
A chave de API sozinha não distingue você do atacante que gravou uma chamada sua: a mensagem é idêntica. Assinando, cada requisição só vale uma vez e só dentro de uma janela curta de tempo.
Opcional hoje
X-Signature. Integrações que não assinam continuam funcionando como sempre. Recomendamos adotar.Headers
X-SignaturestringopcionalHMAC-SHA256 da string canônica, em hexadecimal minúsculo.
X-NoncestringopcionalValor aleatório de uso único, 16 a 128 caracteres em [A-Za-z0-9_-]. Gere com CSPRNG.
X-TimestampnumberopcionalEpoch em segundos (ou milissegundos). Aceito dentro de 5 minutos do relógio do servidor.
| Campo | Tipo | Descrição |
|---|---|---|
X-Signatureopcional | string | HMAC-SHA256 da string canônica, em hexadecimal minúsculo. |
X-Nonceopcional | string | Valor aleatório de uso único, 16 a 128 caracteres em [A-Za-z0-9_-]. Gere com CSPRNG. |
X-Timestampopcional | number | Epoch em segundos (ou milissegundos). Aceito dentro de 5 minutos do relógio do servidor. |
String canônica
METHOD
PATH
TIMESTAMP
NONCE
SHA256(BODY)Cinco linhas separadas por \n. PATH é só o caminho (/api/v1/gateway), sem host nem query. SHA256(BODY) é o hash hexadecimal do corpo exatamente como enviado — serializar de novo muda espaços e ordem de chaves, e a assinatura não bate. O segredo do HMAC é a sua própria chave sk_.
Exemplo em Node.js
import crypto from "node:crypto";
const apiKey = process.env.LOFYPAY_SECRET_KEY;
const body = JSON.stringify({ amount: 199.9, method: "credit_card", /* ... */ });
// CSPRNG. Math.random() não serve: é previsível e o nonce deixa de proteger.
const nonce = crypto.randomBytes(32).toString("base64url");
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash("sha256").update(body, "utf8").digest("hex");
const canonical = ["POST", "/api/v1/gateway", timestamp, nonce, bodyHash].join("
");
const signature = crypto.createHmac("sha256", apiKey).update(canonical, "utf8").digest("hex");
await fetch("https://app.lofypay.com/api/v1/gateway", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`,
"X-Signature": signature,
"X-Nonce": nonce,
"X-Timestamp": timestamp,
"Idempotency-Key": "PEDIDO-123456",
},
body, // o MESMO string que entrou no hash
});Nonce e Idempotency-Key resolvem problemas diferentes
X-Nonceanti-replayImpede que a MESMA requisição seja reenviada. Cada nonce vale uma vez, por 5 minutos.
Idempotency-Keyanti-duplicidadeImpede que a MESMA operação vire duas cobranças. Repetir devolve o resultado original.
| Campo | Tipo | Descrição |
|---|---|---|
X-Nonce | anti-replay | Impede que a MESMA requisição seja reenviada. Cada nonce vale uma vez, por 5 minutos. |
Idempotency-Key | anti-duplicidade | Impede que a MESMA operação vire duas cobranças. Repetir devolve o resultado original. |
Ao retentar, troque o nonce e mantenha a chave
X-Nonce novo e reenvie com a MESMA Idempotency-Key. Repetir o nonce devolve 409; trocar a chave de idempotência cria uma segunda cobrança.Erros da assinatura
missing_headers401Assinou sem enviar X-Nonce ou X-Timestamp, ou o nonce está fora do formato aceito.
stale_timestamp401Timestamp fora da janela de 5 minutos. Confira o relógio do seu servidor.
bad_signature401A assinatura não confere. Quase sempre é o corpo reserializado ou o PATH com host junto.
replay409Nonce já usado — ou o registro anti-replay ficou indisponível. Gere um novo e tente de novo.
| Campo | Tipo | Descrição |
|---|---|---|
missing_headers | 401 | Assinou sem enviar X-Nonce ou X-Timestamp, ou o nonce está fora do formato aceito. |
stale_timestamp | 401 | Timestamp fora da janela de 5 minutos. Confira o relógio do seu servidor. |
bad_signature | 401 | A assinatura não confere. Quase sempre é o corpo reserializado ou o PATH com host junto. |
replay | 409 | Nonce já usado — ou o registro anti-replay ficou indisponível. Gere um novo e tente de novo. |
Códigos de erroLink para esta seção
Todas as rotas respondem JSON. Trate primeiro o status HTTP e depois o corpo.
400Bad RequestCampo obrigatório ausente ou inválido. No status, idtransaction desconhecido.
401UnauthorizedChave ausente ou inválida. Códigos:
missing_key,public_key_used,invalid_format,key_not_found,key_revoked.403ForbiddenIP fora da lista permitida da chave,
live_key_not_allowedao chamar o sandbox com chave live, oucard_api_not_enabled— a conta não tem a API de Cartão liberada.404Not FoundRecurso inexistente ou de outra conta (saques e transações de teste).
409ConflictJá existe um saque em andamento para a conta.
422UnprocessableRequisição válida, mas recusada: saldo insuficiente ou adquirente rejeitou.
429Too Many RequestsLimite de requisições ou cooldown. Respeite o header
Retry-After.500Internal ErrorFalha inesperada. Repita com backoff e, se persistir, fale com o suporte informando o x-request-id.
502Bad GatewayA processadora não gerou a cobrança (
provider_error, o motivo vem emmessage), nenhuma adquirente disponível no momento, ou resultado incerto numa cobrança no cartão. Nada foi cobrado; tente de novo em instantes.503Service UnavailableBanco de dados indisponível:
BALANCE_UNAVAILABLEna consulta de saldo e{ "ok": false }no health check. Tente de novo em instantes.
| Campo | Tipo | Descrição |
|---|---|---|
400 | Bad Request | Campo obrigatório ausente ou inválido. No status, idtransaction desconhecido. |
401 | Unauthorized | Chave ausente ou inválida. Códigos: missing_key, public_key_used, invalid_format, key_not_found, key_revoked. |
403 | Forbidden | IP fora da lista permitida da chave, live_key_not_allowed ao chamar o sandbox com chave live, ou card_api_not_enabled — a conta não tem a API de Cartão liberada. |
404 | Not Found | Recurso inexistente ou de outra conta (saques e transações de teste). |
409 | Conflict | Já existe um saque em andamento para a conta. |
422 | Unprocessable | Requisição válida, mas recusada: saldo insuficiente ou adquirente rejeitou. |
429 | Too Many Requests | Limite de requisições ou cooldown. Respeite o header Retry-After. |
500 | Internal Error | Falha inesperada. Repita com backoff e, se persistir, fale com o suporte informando o x-request-id. |
502 | Bad Gateway | A processadora não gerou a cobrança (provider_error, o motivo vem em message), nenhuma adquirente disponível no momento, ou resultado incerto numa cobrança no cartão. Nada foi cobrado; tente de novo em instantes. |
503 | Service Unavailable | Banco de dados indisponível: BALANCE_UNAVAILABLE na consulta de saldo e { "ok": false } no health check. Tente de novo em instantes. |
Códigos de cartão, boleto e cripto
provider_error502A processadora recusou criar a cobrança (Pix, boleto ou cartão). Nada foi cobrado; o motivo dela vem em message. Pode repetir.
card_api_not_enabled403A conta não tem a API de Cartão liberada. Fale com o suporte da LofyPay.
invalid_card400Token do cartão ausente, inválido, expirado ou gerado com outra chave Stripe. Gere um novo
pm_...com a chave pública da LofyPay.card_token_required400A requisição mandou número, validade ou CVV. Tokenize no navegador com o Stripe.js e envie
card.token.invalid_installments400Parcelas acima do permitido. O cartão é só à vista: envie
installments= 1.billing_address_required400Cobrança no cartão sem endereço do titular. Envie
client.address.zipcode(8 dígitos) eclient.address.number. A mensagem diz qual dos dois falta.card_declined200A operadora recusou. Vem com HTTP 200 porque a requisição foi válida; o que falhou foi a autorização. Não repita com o mesmo cartão.
payment_declined200Recusada pela nossa análise antifraude antes de ir à adquirente.
card_charge_unknown502Resultado INCERTO: a cobrança pode ter sido criada. Não recobre — consulte o status antes de qualquer retentativa.
INVALID_WALLET400Saque em cripto: carteira fora do formato BEP-20 (
0x+ 40 hex), contrato do token ou endereço zero.CRYPTO_LIQUIDITY422Saque em cripto: sem USDT suficiente para o valor agora. Tente um valor menor ou aguarde.
boleto_failed200/502Não foi possível emitir o boleto. Com 502, o resultado é incerto e vale consultar antes de reemitir.
no_active_card_processor503Nenhuma processadora de cartão ativa no momento.
no_active_boleto_processor503Nenhuma processadora de boleto ativa no momento.
| Campo | Tipo | Descrição |
|---|---|---|
provider_error | 502 | A processadora recusou criar a cobrança (Pix, boleto ou cartão). Nada foi cobrado; o motivo dela vem em message. Pode repetir. |
card_api_not_enabled | 403 | A conta não tem a API de Cartão liberada. Fale com o suporte da LofyPay. |
invalid_card | 400 | Token do cartão ausente, inválido, expirado ou gerado com outra chave Stripe. Gere um novo pm_... com a chave pública da LofyPay. |
card_token_required | 400 | A requisição mandou número, validade ou CVV. Tokenize no navegador com o Stripe.js e envie card.token. |
invalid_installments | 400 | Parcelas acima do permitido. O cartão é só à vista: envie installments = 1. |
billing_address_required | 400 | Cobrança no cartão sem endereço do titular. Envie client.address.zipcode (8 dígitos) e client.address.number. A mensagem diz qual dos dois falta. |
card_declined | 200 | A operadora recusou. Vem com HTTP 200 porque a requisição foi válida; o que falhou foi a autorização. Não repita com o mesmo cartão. |
payment_declined | 200 | Recusada pela nossa análise antifraude antes de ir à adquirente. |
card_charge_unknown | 502 | Resultado INCERTO: a cobrança pode ter sido criada. Não recobre — consulte o status antes de qualquer retentativa. |
INVALID_WALLET | 400 | Saque em cripto: carteira fora do formato BEP-20 (0x + 40 hex), contrato do token ou endereço zero. |
CRYPTO_LIQUIDITY | 422 | Saque em cripto: sem USDT suficiente para o valor agora. Tente um valor menor ou aguarde. |
boleto_failed | 200/502 | Não foi possível emitir o boleto. Com 502, o resultado é incerto e vale consultar antes de reemitir. |
no_active_card_processor | 503 | Nenhuma processadora de cartão ativa no momento. |
no_active_boleto_processor | 503 | Nenhuma processadora de boleto ativa no momento. |
502 no cartão não é recusa
card_charge_unknown significa que perdemos a resposta da adquirente, não que o cartão foi negado. A cobrança pode existir. Cobrar de novo pode cobrar duas vezes — consulte o status com a mesma external_reference antes de repetir.{
"success": false,
"error": "Descrição legível do problema",
"code": "invalid_format"
}As rotas de cashout usam o envelope próprio com code, message e retryable. A rota de status devolve apenas { "error": "..." }.
SegurançaLink para esta seção
O que a LofyPay faz do lado dela e o que esperamos do seu lado.
Secret Key só no servidor
Nunca coloque a sk_live_ em apps mobile, front-end ou repositórios. Use variáveis de ambiente e rotacione a chave em /gateway se suspeitar de vazamento.
Lista de IPs
Restrinja cada chave aos IPs dos seus servidores. Requisições de fora recebem 403 mesmo com a chave correta.
Webhooks assinados
Valide sempre a X-LofyPay-Signature e rejeite timestamps fora da janela de 5 minutos. Nunca libere pedido só pelo corpo do POST.
Idempotência
Use Idempotency-Key nos saques e deduplique webhooks pelo X-LofyPay-Id. Retentativas não devem gerar efeito duplo.
Concilie pelo status
Antes de entregar o produto, confirme na rota de status que a transação está PAID_OUT. Webhook é aviso, status é a fonte.
Rastreabilidade
Toda resposta carrega o header x-request-id. Guarde esse valor nos seus logs para o suporte localizar a chamada.
Integrar com IA (vibe coding)Link para esta seção
Programa com Cursor, Lovable, Bolt, v0, Windsurf, Claude ou ChatGPT? Copie o prompt abaixo e cole na ferramenta: ele já traz os endpoints, a autenticação, a validação do webhook e as regras de segurança, para a IA montar a integração inteira do jeito certo.
- 1
Gere sua chave
Crie uma
sk_test_em /gateway para testar sem dinheiro real. - 2
Cole o prompt
Copie o prompt abaixo e cole na sua ferramenta de IA, dentro do projeto que vai receber a integração.
- 3
Teste no sandbox
Simule o pagamento com
/api/v1/sandbox/paye só depois troque parask_live_.
Você é um engenheiro sênior. Integre a API de pagamentos da LofyPay (gateway brasileiro de PIX) neste projeto, do jeito certo e seguro. Antes de escrever código, identifique a stack do projeto (framework, linguagem, onde roda o servidor) e me diga em 3 linhas o plano. Se faltar alguma informação, pergunte antes de inventar.
# Contexto da API
- Base URL: https://app.lofypay.com/api/v1
- Documentação completa: https://docs.lofypay.com
- REST + JSON. Valores sempre em REAIS (number, ex.: 100.50), nunca em centavos.
- Autenticação: header "Authorization: Bearer <SECRET_KEY>". A chave começa com sk_live_ (produção) ou sk_test_ (sandbox). A mesma URL serve os dois ambientes; o prefixo da chave decide.
- Rate limit: 30 req/min por IP e 120 req/min por chave. Em 429, respeite o header Retry-After.
# Regras de segurança (obrigatórias)
1. A SECRET KEY só existe no SERVIDOR, em variável de ambiente (LOFYPAY_SECRET_KEY). Nunca no front-end, no app mobile, no repositório ou em logs.
2. Toda chamada à LofyPay sai do backend (rota de API / server action / função serverless). O navegador fala só com o meu backend.
3. Nunca libere pedido só porque chegou um POST no webhook: valide a assinatura (canal assinado) OU confirme no endpoint de status antes de entregar.
4. Seja idempotente: o mesmo pagamento confirmado duas vezes não pode entregar o produto duas vezes.
# 1) Criar cobrança PIX
POST https://app.lofypay.com/api/v1/gateway
Headers: Authorization: Bearer <SECRET_KEY>, Content-Type: application/json, Idempotency-Key: <id único do pedido> (opcional, evita cobrança duplicada em retentativa)
Body:
{
"amount": 100.50,
"method": "pix",
"external_reference": "PEDIDO-123", // id do pedido no meu sistema (volta no webhook)
"notification_url": "https://MEU-SITE/api/webhooks/lofypay-pedido", // opcional
"expiration": 1800, // opcional, segundos (60 a 86400)
"client": { "name": "Nome do comprador", "document": "CPF/CNPJ só dígitos", "email": "email@exemplo.com" },
"trackingParameters": { "utm_source": "...", "utm_campaign": "...", "utm_medium": "...", "utm_content": "...", "utm_term": "...", "src": null, "sck": null } // opcional (Utmify)
}
Resposta de sucesso:
{ "status": "success", "idTransaction": "52fc5262-...", "paymentCode": "000201...", "paymentCodeBase64": "iVBOR..." (opcional) }
ATENÇÃO: HTTP 200 NÃO garante sucesso. Trate como sucesso só se status for "success" (ou "OK" no sandbox) E vier idTransaction. Se vier { "status": "error", "message": "..." }, mostre a mensagem e não abra o checkout.
- paymentCode é o "copia e cola". Gere o QR Code a partir dele (biblioteca de QR no front). paymentCodeBase64 nem sempre vem.
- Salve no banco: idTransaction, external_reference, valor, status "pendente".
- Valor mínimo por PIX pode existir por processadora (erro "deposit_below_minimum"); mostre a mensagem da API.
# 2) Tela de pagamento
- Mostre o QR Code, o copia e cola com botão "Copiar", o valor e um aviso de expiração.
- Enquanto estiver aberta, consulte o MEU backend a cada 5 s (que consulta a LofyPay ou lê o banco atualizado pelo webhook). Pare quando pagar ou expirar.
# 3) Consultar status (fonte da verdade)
POST https://app.lofypay.com/api/v1/status
Headers: Authorization: Bearer <SECRET_KEY>, Content-Type: application/json
Body: { "idtransaction": "<idTransaction>" }
Resposta: { "status": "PAID_OUT" }
Status possíveis: WAITING_FOR_APPROVAL (aguardando), PAID_OUT (PAGO), EXPIRED, REFUNDED, FAILED. Só PAID_OUT é pago.
# 4) Webhooks (dois canais, use pelo menos um)
Canal A — notification_url (por cobrança, SEM assinatura):
POST no meu endpoint com { "status": "PAID", "idTransaction": "...", "amount": 100.50, "external_reference": "PEDIDO-123", "paid_at": "..." }
Como não é assinado: ao receber, chame /api/v1/status com o idTransaction e só marque como pago se voltar PAID_OUT.
Canal B — endpoints cadastrados no painel (Gateway/API > Webhooks), ASSINADOS (recomendado):
Envelope: { "event": "payment.approved", "sent_at": "...", "data": { "idTransaction": "...", "external_reference": "...", "amount": 100.50, "payment_method": "pix", ... } }
Eventos: payment.pending, payment.approved, payment.refunded, payment.cancelled, saque.requested, saque.paid, saque.failed, med.opened, med.updated, med.resolved.
Headers: X-LofyPay-Event, X-LofyPay-Timestamp, X-LofyPay-Id (id da entrega, use para deduplicar), X-LofyPay-Signature = "t=<unix>,v1=<hex>".
Validação: HMAC-SHA256 com o secret DO ENDPOINT (variável LOFYPAY_WEBHOOK_SECRET, NÃO é a sk_live_) sobre a string "<t>.<corpo cru>". Compare em tempo constante e rejeite se |agora - t| > 300 s. Use o corpo CRU (sem JSON.parse antes) para o HMAC bater.
Exemplo Node:
const parts = Object.fromEntries(sig.split(",").map(p => p.split("=")));
const esperado = crypto.createHmac("sha256", process.env.LOFYPAY_WEBHOOK_SECRET).update(parts.t + "." + rawBody).digest("hex");
válido = Math.abs(Date.now()/1000 - Number(parts.t)) <= 300 && crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(parts.v1));
Regras: responda 200 em até 8 s e processe depois (a LofyPay reenvia em 1 min, 5 min, 30 min, 2 h e 12 h se não receber 2xx). Deduplique por X-LofyPay-Id e pelo idTransaction já pago.
# 5) Sandbox (teste antes de ir para produção)
Use uma chave sk_test_. Para simular o pagamento:
POST https://app.lofypay.com/api/v1/sandbox/pay Body: { "api-key": "sk_test_...", "idTransaction": "<id>", "status": "paid" }
(status aceita paid, failed, expired, pending). Isso dispara o webhook de teste (header X-Lofypay-Environment: test).
# 6) Saque PIX (só se eu pedir)
POST https://app.lofypay.com/api/v1/cashout com Idempotency-Key obrigatório na prática:
{ "amount": 250.00, "name": "Nome do favorecido", "cpf": "CPF/CNPJ", "keypix": "chave", "tipo_chave": "CPF|CNPJ|EMAIL|TELEFONE|EVP" }
A resposta "PROCESSING" NÃO é pago: o fim vem pelo evento saque.paid ou saque.failed. Erros trazem { code, message, retryable }.
# 7) Erros
- 400 campo inválido · 401 chave inválida (missing_key, public_key_used, invalid_format, key_not_found, key_revoked) · 403 IP/permissão · 409 conflito/saque em andamento · 422 recusado (ex.: saldo) · 429 limite (Retry-After) · 502 processadora não gerou (nada foi cobrado, pode repetir).
- Guarde o header x-request-id das respostas nos logs para o suporte.
# O que entregar
1. Variáveis de ambiente: LOFYPAY_SECRET_KEY e LOFYPAY_WEBHOOK_SECRET (e .env.example sem valores reais).
2. Um módulo/serviço de backend "lofypay" com: criarCobrancaPix(pedido), consultarStatus(idTransaction) e verificarWebhook(rawBody, headers).
3. Rota do backend para criar o PIX do pedido e devolver ao front só o necessário (copia e cola, QR, valor, expiração). Nunca devolva a chave.
4. Rota de webhook que valida a assinatura (ou confirma no /status), marca o pedido como pago de forma idempotente e responde 200 rápido.
5. Tela de pagamento com QR Code, copiar, contagem de expiração e confirmação automática.
6. Tratamento de erro legível para o usuário e logs com x-request-id.
7. Um passo a passo curto de como testar no sandbox (sk_test_ + /api/v1/sandbox/pay) e de como ir para produção (trocar para sk_live_ e cadastrar o webhook no painel).
Checklist final antes de terminar: a chave não aparece em nenhum arquivo do front nem no repositório; o webhook rejeita assinatura inválida; pagar duas vezes o mesmo aviso não duplica entrega; o fluxo inteiro funciona no sandbox.Para agentes que leem a web
https://docs.lofypay.com/api/docs/prompt. Em ferramentas que aceitam URL de contexto (como o @docs do Cursor), aponte para ele ou para https://docs.lofypay.com/llms.txt.Revise o que a IA gerou
sk_live_ não pode aparecer em nenhum arquivo do front-end nem no repositório, e o webhook precisa recusar assinatura inválida.