Documentação da API v1

Integre PIX na sua aplicação em minutos

Gere cobranças, consulte o status, receba webhooks assinados e faça saques por chave PIX com uma API REST simples. Esta página cobre os endpoints, os eventos e as boas práticas de segurança.

URL basehttps://app.lofypay.com/api/v1
  • API REST

    JSON puro, sem SDK obrigatório

  • PIX em tempo real

    Copia e cola devolvido na hora

  • Webhooks assinados

    Eventos de pagamento, saque e MED

  • Camadas de segurança

    HMAC, rate limit e allowlist de IP

Nesta páginamostrar

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.

LiveProdução

Tráfego real, taxas reais e movimentação de dinheiro de verdade.

URL base
https://app.lofypay.com/api/v1
Prefixo
sk_live_
TestSandbox

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

Não existe domínio separado para o sandbox. A própria chave 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.

POSThttps://app.lofypay.com/api/v1/sandbox/pay
JSON
{
  "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.

Webhook de teste disparado (status: paid)
{
  "event": "pix.paid",
  "environment": "test",
  "test": true,
  "idTransaction": "52fc5262-...",
  "externalReference": "PEDIDO-123456",
  "status": "paid"
}

O webhook de teste tem formato próprio

Ele vai com o header 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:

  1. Authorization: Bearer sk_live_... (recomendado)
  2. x-api-key: sk_live_... (alternativa por header)
  3. "api-key": "sk_live_..." no body JSON (compatibilidade)
Exemplo por header (recomendado)
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" } }'
Exemplo por body (compatibilidade)
{
  "api-key": "sk_live_...sua_secret_key...",
  "amount": 100.50
}

Nunca exponha a secret key

A 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

Em caso de chave inválida, a resposta vem com um 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.

POSThttps://app.lofypay.com/api/v1/gateway

Parâmetros do body

  • api-keystringobrigatório

    Sua chave (sk_live_ ou sk_test_). Dispensável se você autenticar por header.

  • amountnumberobrigatório

    Valor 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ório

    Dados do pagador. O objeto é obrigatório. Campos ausentes recebem valores neutros.

  • client.namestringopcional

    Nome completo do pagador. Recomendado. Sem ele a cobrança é registrada como genérica.

  • client.documentstringopcional

    CPF ou CNPJ do pagador, só dígitos. Recomendado para conciliação e defesa de MED.

  • client.emailstringopcional

    E-mail do pagador. Recomendado para conciliação.

  • external_referencestringopcional

    ID do pedido no seu sistema (exemplo: PEDIDO-1050). Volta no webhook.

  • notification_urlurlopcional

    URL HTTPS que recebe um POST automático quando o pagamento confirmar.

  • expirationnumberopcional

    Validade da cobrança em segundos, de 60 a 86400.

  • split.percentualnumberopcional

    Percentual do valor bruto a destinar para outra conta, de 0 a 100.

  • split.destinostringopcional

    user_id da conta que recebe o split.

  • metadata.order_idstringopcional

    Alternativa a external_reference.

Idempotency-Key

O header 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

JSON
{
  "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
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"
    }
  }'
Node.js (fetch)
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

JSON
{
  "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

Se a adquirente recusar a criação, o erro pode chegar com HTTP 200 e corpo { "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

30 requisições por minuto por IP e 120 por minuto por chave. Acima disso a API responde 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

A API de Cartão requer liberação prévia da LofyPay. Caso sua conta ainda não possua acesso, entre em contato com o suporte. Sem liberação, a chamada responde 403 com card_api_not_enabled. Pix e boleto não dependem dessa liberação.
POSThttps://app.lofypay.com/api/v1/gateway

Parâmetros do body

  • api-keystringobrigatório

    Sua chave sk_. Dispensável se autenticar por header.

  • amountnumberobrigatório

    Valor total em reais.

  • methodenumobrigatório

    Use "credit_card".

  • cardobjectobrigatório

    Cartão. É a presença deste objeto que define a cobrança como cartão.

  • card.tokenstringobrigatório

    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_namestringopcional

    Nome impresso no cartão.

  • installmentsnumberopcional

    Sempre 1: o cartão é só à vista no momento. Outro valor responde 400 com invalid_installments.

  • remote_ipstringopcional

    IP do DISPOSITIVO do pagador, não do seu servidor.

  • client.namestringobrigatório

    Nome do titular.

  • client.emailstringobrigatório

    E-mail do titular.

  • client.documentstringobrigatório

    CPF ou CNPJ do titular, só dígitos.

  • client.phonestringobrigatório

    Telefone do titular, 10 ou 11 dígitos.

  • client.address.zipcodestringobrigatório

    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.numberstringobrigatório

    Número do endereço de cobrança do titular. Use "S/N" quando não houver.

  • client.address.complementstringopcional

    Complemento do endereço do titular.

  • client.address.countrystringopcional

    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_referencestringopcional

    ID do pedido no seu sistema. Volta no webhook.

  • notification_urlurlopcional

    URL HTTPS que recebe o POST quando o pagamento confirmar.

O número do cartão nunca passa pelo seu servidor

O cartão é digitado num campo da Stripe na sua página, e a API recebe só o token 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

Nome, e-mail, CPF, telefone e o endereço de cobrança (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

Envie 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

JSON
{
  "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.

HTML + Stripe.js
<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

O token só existe na conta Stripe que o criou. Gerado com outra chave, a cobrança responde 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

JSON
{
  "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

JSON
{
  "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

Não devolvemos o motivo exato da operadora (CVV incorreto, validade errada, saldo insuficiente). Repassar isso transformaria o checkout num verificador de cartões: com um número em mãos, seria possível descobrir validade e CVV testando um campo por vez e lendo a resposta. O motivo original fica registrado do nosso lado para suporte.

Prazo, parcelamento e bandeiras

  • Parcelamentoindisponível

    O cartão é só à vista no momento, na API e no checkout. Envie installments = 1 ou omita.

  • BandeirasVisa · 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.

POSThttps://app.lofypay.com/api/v1/gateway

Parâmetros do body

  • api-keystringobrigatório

    Sua chave sk_.

  • amountnumberobrigatório

    Valor do boleto em reais.

  • methodenumobrigatório

    Use "boleto".

  • client.namestringobrigatório

    Nome do pagador. Vai impresso no boleto.

  • client.documentstringobrigatório

    CPF ou CNPJ do pagador, só dígitos. Vai impresso no boleto.

  • client.emailstringopcional

    E-mail do pagador. Recomendado para conciliação.

  • client.phonestringopcional

    Telefone do pagador.

  • client.address.zipcodestringopcional

    CEP do pagador. Quando informado, compõe o endereço impresso no boleto.

  • client.address.numberstringopcional

    Número do endereço do pagador.

  • external_referencestringopcional

    ID do pedido no seu sistema. Volta no webhook.

  • notification_urlurlopcional

    URL HTTPS que recebe o POST quando o boleto for pago.

Nome e CPF são obrigatórios

O boleto é pago longe do seu site, num banco. Sem nome e documento, quem recebe não tem como identificar quem pagou — e o boleto não pode ser emitido.

Exemplo de requisição

JSON
{
  "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

JSON
{
  "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 dias

    Contados da emissão. Depois disso o boleto não pode mais ser pago e a cobrança fica sem efeito.

  • Confirmaçãoaté 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 Pixaceito

    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.

POSThttps://app.lofypay.com/api/v1/status
JSON
{
  "idtransaction": "52fc5262-4063-4900-933b-55e6985096a1"
}

Resposta

JSON
{
  "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

Polling em loop consome rate limit. Configure 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:

POSThttps://app.lofypay.com/api/v1/gateway
  • splitarrayopcional

    Lista de recebedores. Máximo configurado pela LofyPay (padrão: 10). Só PIX.

  • split[].accountIdstringobrigatório

    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[].percentagenumberopcional

    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[].amountintegeropcional

    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

Cada recebedor usa um dos dois. Enviar os dois (ou nenhum) no mesmo item é recusado. Recebedores percentuais e fixos podem ser misturados na mesma cobrança.
Exemplo — percentual e valor fixo
{
  "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):

JSON
{
  "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:

Exemplo de distribuição
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)
  • Centavosinteiro

    Todo cálculo é feito em centavos inteiros, sem ponto flutuante.

  • Percentualmeio para cima

    Cada percentual é arredondado ao centavo mais próximo; meio centavo sobe (48,975 → 48,98).

  • Sobrasua 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ínimoR$ 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.

JSON
{
  "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" }
    ]
  }
}
  • PENDINGstatus

    Cobrança criada, aguardando pagamento.

  • COMPLETEDstatus

    Pagamento confirmado e valores creditados.

  • REVERSEDstatus

    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.

JSON
"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_enabled403

    Conta sem liberação de split.

  • split_disabled403

    Split temporariamente desativado na plataforma.

  • split_method_not_supported400

    Split só em PIX.

  • split_invalid / split_invalid_account400

    Formato da lista ou do accountId inválido.

  • split_account_not_found400

    accountId não existe.

  • split_account_cannot_receive400

    Conta inativa, bloqueada, suspensa ou sem permissão para receber split.

  • split_self_account400

    A sua própria conta como recebedora.

  • split_duplicate_account400

    A mesma conta mais de uma vez.

  • split_style_mismatch400

    Conta recebedora de outro tipo (White/Black).

  • split_processor_mismatch400

    Conta recebedora opera em outra processadora. Split só entre contas da mesma processadora.

  • split_both_percentage_and_amount400

    percentage e amount no mesmo item.

  • split_missing_percentage_or_amount400

    Nem percentage nem amount.

  • split_invalid_percentage / split_invalid_amount400

    Zero, negativo, acima de 100%, mais de 2 casas ou amount não inteiro.

  • split_percentage_over_limit400

    Soma dos percentuais acima do permitido.

  • split_exceeds_net400

    Soma do split maior que o valor líquido da cobrança.

  • split_amount_too_small400

    Algum recebedor ficaria com R$ 0,00.

  • split_too_many_recipients400

    Mais recebedores que o limite.

  • split_limit_exceeded400

    Limite por transação, diário ou mensal de split atingido.

  • split_conflict_account_split400

    A conta tem repasse automático configurado pela LofyPay.

  • split_unavailable / split_error503 / 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.

Payload recebido
{
  "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.

Cartão ou boleto confirmado
{
  "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

Este webhook avisa que o pagamento foi CONFIRMADO. No cartão e no boleto o valor ainda não está no saldo sacável — ver Liquidação e saldo. Libere o pedido aqui; espere a liquidação para contar o dinheiro.
Estorno ou chargeback
{
  "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

Esse POST não carrega assinatura. Qualquer um pode enviar um corpo idêntico para o seu endpoint. Antes de liberar o pedido, confirme de forma independente: consulte /api/v1/status com o idTransaction recebido e só libere se voltar PAID_OUT.

Confirme o recebimento com HTTP 200

Se sua resposta não for 2xx, a LofyPay tenta de novo com intervalos crescentes (1 min, 5 min, 30 min, 2 h e 12 h), até 5 tentativas. Responda 200 rápido e processe de forma assíncrona.

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.

Envelope de todo evento
{
  "event": "payment.approved",
  "sent_at": "2026-06-01T14:30:12.000Z",
  "data": { ... }
}
  • payment.pendingevento

    Cobrança criada e aguardando o pagador. Traz expires_at com a validade do QR Code.

  • payment.approvedevento

    Cobrança confirmada, em qualquer método. Leia payment_method para saber qual.

  • payment.refundedevento

    Cobrança estornada ou devolvida. O crédito foi revertido.

  • payment.cancelledevento

    Cobrança expirou sem pagamento. Campo reason com valor expired.

  • saque.requestedevento

    Saque criado via API. status vem PROCESSING ou PENDING_REVIEW.

  • saque.paidevento

    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.failedevento

    Saque recusado ou falhou depois de aceito. O saldo já foi devolvido à conta. Campo reason traz o motivo.

  • med.openedevento

    Abriu um MED (infração PIX ou devolução pelo Banco Central) sobre uma transação sua.

  • med.updatedevento

    MED mudou de status, por exemplo em análise ou defesa enviada.

  • med.resolvedevento

    MED encerrado. Campo resolution: lost (procedente) ou kept (improcedente).

Headers enviados no canal registrado

  • Content-Typestring

    application/json

  • X-LofyPay-Eventstring

    Tipo do evento, por exemplo payment.approved.

  • X-LofyPay-Timestampstring

    Unix em segundos de quando o evento foi assinado.

  • X-LofyPay-Signaturestring

    Assinatura no formato t=<unix>,v1=<hmac>. O HMAC-SHA256 é calculado sobre <timestamp>.<body> com o secret do endpoint.

  • X-LofyPay-Idstring

    UUID único da entrega. Use para deduplicar.

  • User-Agentstring

    LofyPay-Webhook/1.0

Não aceite payment.approved sem verificar

O corpo, sozinho, não prova nada. Qualquer um pode fazer um 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

Em /gateway, aba Webhooks, no cartão do endpoint, clique no olho em "Secret de assinatura". Cada endpoint tem o seu. Não é a sua Secret Key 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

Node.js (Express)
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

A LofyPay espera até 8 segundos pela sua resposta. Se não vier um 2xx nesse tempo, ela reenvia com intervalos crescentes (1 min, 5 min, 30 min, 2 h e 12 h), até 5 tentativas. Responda 200 na hora e processe depois, de forma assíncrona. Cada reentrega é assinada de novo com timestamp atual, mas mantém o mesmo X-LofyPay-Id e o mesmo corpo. Faça seu processamento idempotente.

Testando antes de ir para produção

Em /gateway, aba Webhooks, o botão "Testar" dispara um evento de teste assinado para o seu endpoint. Você pode validar a assinatura com o mesmo código acima.
Exemplo: med.opened
{
  "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

Assinar <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

POSThttps://app.lofypay.com/api/v1/cashout

O caminho /api/c1/cashout continua aceito como alias para integrações antigas. Use /api/v1/cashout em novos projetos.

  • api-keystringobrigatório

    Sua Secret Key sk_live_ ou sk_test_. Também pode ir no header Authorization: Bearer.

  • amountnumberobrigatório

    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.

  • namestringobrigatório

    Nome completo do favorecido.

  • cpfstringobrigatório

    CPF ou CNPJ do favorecido, com ou sem pontuação.

  • keypixstringobrigatório

    Chave PIX de destino.

  • tipo_chavestringobrigatório

    Tipo da chave: CPF, CNPJ, EMAIL, TELEFONE ou EVP (chave aleatória).

  • Idempotency-Keyheaderopcional

    Header opcional. Repetir a chamada com a mesma chave devolve o mesmo saque em vez de criar outro. Recomendado para retentativas.

Corpo da requisição
{
  "api-key": "sk_live_...",
  "amount": 250.00,
  "name": "Maria da Silva",
  "cpf": "123.456.789-09",
  "keypix": "maria@exemplo.com",
  "tipo_chave": "EMAIL"
}
cURL
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"
  }'
Resposta 200: saque aceito
{
  "success": true,
  "code": "SUCCESS",
  "message": "Saque em processamento",
  "retryable": false,
  "status": "PROCESSING",
  "withdrawalId": 4321,
  "id": 4321
}
Resposta 422: saldo insuficiente
{
  "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

O 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

GEThttps://app.lofypay.com/api/v1/cashout

Envie 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.

  • withdrawalIdnumberopcional

    Id devolvido no POST.

  • idempotencyKeystringopcional

    A Idempotency-Key usada na criação. Também aceita como externalReference.

  • transactionIdstringopcional

    Identificador da transação na adquirente, quando disponível.

cURL
curl "https://app.lofypay.com/api/v1/cashout?withdrawalId=4321" \
  -H "Authorization: Bearer sk_live_..."
Resposta 200: saque liquidado
{
  "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

Criar saque: 10 por minuto por IP e 30 por minuto por chave. Consultar: 60 por minuto por IP e 120 por minuto por chave. Acima disso a API responde 429 com o header 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.

POSThttps://app.lofypay.com/api/v1/cashout/qrcode

Só para contas white

Pagar código pela API é exclusivo de contas white. Em conta black a resposta é 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ório

    Sua Secret Key sk_live_. Também pode ir no header Authorization: Bearer.

  • brcodestringobrigatório

    O Pix copia e cola inteiro, começando por 000201. Quebras de linha do copiar e colar são ignoradas.

  • amountnumberopcional

    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.

  • externalReferencestringopcional

    Sua referência. Sem o header Idempotency-Key, ela é usada como chave de idempotência.

  • Idempotency-Keyheaderopcional

    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
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"
  }'
Resposta 200: pagamento enviado
{
  "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_BRCODE400

    O texto não é um Pix copia e cola válido, está incompleto ou foi alterado (CRC não confere).

  • INVALID_AMOUNT400

    O amount enviado é diferente do valor do código, ou o valor é inválido.

  • MISSING_FIELDS400

    Faltou o brcode, ou o código não tem valor e o amount não veio.

  • API_CASHOUT_DISABLED403

    Conta black, saque via API não liberado para a conta, conta suspensa ou verificação pendente.

  • BRCODE_NOT_PAYABLE422

    Código de cobrança já paga, cancelada, vencida ou que não pôde ser consultada.

  • INSUFFICIENT_BALANCE422

    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

Saque em cripto pela API é exclusivo de contas white e segue o mesmo requisito do saque: o saque pela API precisa estar liberado para a conta. Em conta black a resposta é 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

GEThttps://app.lofypay.com/api/v1/cashout/cripto?amount=500

Mostra 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
curl "https://app.lofypay.com/api/v1/cashout/cripto?amount=500" \
  -H "Authorization: Bearer sk_live_..."
Resposta 200
{
  "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"
}
  • amountnumber

    Valor em reais que será convertido em USDT.

  • feenumber

    Taxa total do saque em cripto, em reais. Cobrada por fora.

  • total_debitednumber

    O que sai do seu saldo: amount + fee.

  • usdt_estimatednumber

    USDT estimado a receber, já descontadas as taxas. Arredondado para baixo; não é garantia.

  • rate_brl_per_usdnumber

    Cotação indicativa em reais por dólar, com spread incluído.

  • min_amount / max_amountnumber

    Faixa aceita para amount no momento. Fora dela o saque responde INVALID_AMOUNT.

Sacar

POSThttps://app.lofypay.com/api/v1/cashout/cripto
  • api-keystringobrigatório

    Sua Secret Key sk_live_. Também pode ir no header Authorization: Bearer.

  • amountnumberobrigatório

    Valor em reais a converter em USDT. A taxa é cobrada à parte: sai do saldo amount + taxa.

  • walletstringobrigatório

    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.

  • externalReferencestringopcional

    Sua referência. Sem o header Idempotency-Key, ela é usada como chave de idempotência.

  • Idempotency-Keyheaderopcional

    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

O USDT é enviado só na rede BNB Smart Chain (BEP-20). Uma carteira de outra rede (Tron/TRC-20, Ethereum/ERC-20) ou digitada errado recebe o envio e o valor não volta. Transferência em blockchain não tem estorno.
cURL
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"
  }'
Resposta 200: saque aceito
{
  "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_WALLET400

    Carteira ausente do formato BEP-20, endereço de contrato do token ou endereço zero.

  • INVALID_AMOUNT400

    Valor abaixo do mínimo de saque, fora da faixa aceita no momento, ou baixo demais para sobrar USDT depois das taxas.

  • API_CASHOUT_DISABLED403

    Conta black, saque via API não liberado, chave de teste, conta suspensa ou verificação pendente.

  • INSUFFICIENT_BALANCE422

    O saldo não cobre o valor mais a taxa.

  • CRYPTO_LIQUIDITY422

    Não há USDT suficiente para esse valor agora. Tente um valor menor ou aguarde alguns minutos (retryable: true).

  • PROVIDER_NOT_AVAILABLE502

    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:

Objeto crypto (consulta e saque.paid)
"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.

GEThttps://app.lofypay.com/api/v1/balance

Envie 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
curl "https://app.lofypay.com/api/v1/balance" \
  -H "Authorization: Bearer sk_live_..."
Resposta 200
{
  "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"
}
  • availablenumber

    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.

  • blockednumber

    Total retido que não pode ser sacado: MED em aberto mais retenções feitas pela equipe da LofyPay.

  • blocked_mednumber

    A parte de blocked que está presa em MED. Volta para available se o MED for improcedente.

  • receivablenumber

    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_atstring | null

    Data prevista da próxima liberação de valores a receber. null quando não há nada a receber.

  • as_ofstring

    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

60 por minuto por IP e 120 por minuto por chave, os mesmos da consulta de saque. Para reagir a cada venda ou saque, prefira os webhooks a consultar o saldo em loop.

Health checkLink para esta seção

Verifique se a API está no ar antes de abrir um chamado ou pausar o seu checkout.

GEThttps://app.lofypay.com/api/health
Resposta 200
{ "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:

  • Aprovadostatus

    A operadora autorizou a compra. A venda entra como PAID_OUT e aparece como paga no painel. Nada de saldo ainda.

  • Liquidadosettlement

    A adquirente liberou o valor na conta da LofyPay. É o evento que move o dinheiro, e só ele.

  • Disponívelsaldo

    O valor entrou no saldo sacável do vendedor. Acontece junto com a liquidação.

Venda paga não significa saldo disponível

No cartão, a venda aparece como paga no mesmo instante e o saldo só sobe semanas depois. Enquanto isso o valor fica em A receber, visível em Financeiro. É o comportamento correto: adiantar esse dinheiro seria a LofyPay financiando a janela de liquidação do cartão com caixa próprio.

Prazos por método

  • Piximediato

    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.

  • Boleto1 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_APPROVALstatus

    Cobrança criada, aguardando pagamento. É o estado inicial de Pix e boleto, e do cartão em análise de risco.

  • PAID_OUTstatus

    Pagamento confirmado.

  • CANCELLEDstatus

    Recusada pela análise de risco, captura negada ou cobrança removida.

  • PENDINGliquidação

    Pago, aguardando a adquirente liberar. Só cartão e boleto passam por aqui.

  • AVAILABLEliquidação

    Liberado e somado ao saldo disponível.

  • REVERSEDliquidaçã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

A verificação só roda quando você envia X-Signature. Integrações que não assinam continuam funcionando como sempre. Recomendamos adotar.

Headers

  • X-Signaturestringopcional

    HMAC-SHA256 da string canônica, em hexadecimal minúsculo.

  • X-Noncestringopcional

    Valor aleatório de uso único, 16 a 128 caracteres em [A-Za-z0-9_-]. Gere com CSPRNG.

  • X-Timestampnumberopcional

    Epoch em segundos (ou milissegundos). Aceito dentro de 5 minutos do relógio do servidor.

String canônica

HTTP
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

JavaScript
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-replay

    Impede que a MESMA requisição seja reenviada. Cada nonce vale uma vez, por 5 minutos.

  • Idempotency-Keyanti-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

Perdeu a resposta e quer tentar de novo? Gere um 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_headers401

    Assinou sem enviar X-Nonce ou X-Timestamp, ou o nonce está fora do formato aceito.

  • stale_timestamp401

    Timestamp fora da janela de 5 minutos. Confira o relógio do seu servidor.

  • bad_signature401

    A assinatura não confere. Quase sempre é o corpo reserializado ou o PATH com host junto.

  • replay409

    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 Request

    Campo obrigatório ausente ou inválido. No status, idtransaction desconhecido.

  • 401Unauthorized

    Chave ausente ou inválida. Códigos: missing_key, public_key_used, invalid_format, key_not_found, key_revoked.

  • 403Forbidden

    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.

  • 404Not Found

    Recurso inexistente ou de outra conta (saques e transações de teste).

  • 409Conflict

    Já existe um saque em andamento para a conta.

  • 422Unprocessable

    Requisição válida, mas recusada: saldo insuficiente ou adquirente rejeitou.

  • 429Too Many Requests

    Limite de requisições ou cooldown. Respeite o header Retry-After.

  • 500Internal Error

    Falha inesperada. Repita com backoff e, se persistir, fale com o suporte informando o x-request-id.

  • 502Bad 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.

  • 503Service 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_error502

    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_enabled403

    A conta não tem a API de Cartão liberada. Fale com o suporte da LofyPay.

  • invalid_card400

    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_required400

    A requisição mandou número, validade ou CVV. Tokenize no navegador com o Stripe.js e envie card.token.

  • invalid_installments400

    Parcelas acima do permitido. O cartão é só à vista: envie installments = 1.

  • billing_address_required400

    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_declined200

    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_declined200

    Recusada pela nossa análise antifraude antes de ir à adquirente.

  • card_charge_unknown502

    Resultado INCERTO: a cobrança pode ter sido criada. Não recobre — consulte o status antes de qualquer retentativa.

  • INVALID_WALLET400

    Saque em cripto: carteira fora do formato BEP-20 (0x + 40 hex), contrato do token ou endereço zero.

  • CRYPTO_LIQUIDITY422

    Saque em cripto: sem USDT suficiente para o valor agora. Tente um valor menor ou aguarde.

  • boleto_failed200/502

    Não foi possível emitir o boleto. Com 502, o resultado é incerto e vale consultar antes de reemitir.

  • no_active_card_processor503

    Nenhuma processadora de cartão ativa no momento.

  • no_active_boleto_processor503

    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.
Envelope de erro do gateway
{
  "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. 1

    Gere sua chave

    Crie uma sk_test_ em /gateway para testar sem dinheiro real.

  2. 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. 3

    Teste no sandbox

    Simule o pagamento com /api/v1/sandbox/pay e só depois troque para sk_live_.

Prompt de integração LofyPay
Abrir em texto
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

O mesmo prompt está em texto puro em 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

Confira antes de publicar: a sk_live_ não pode aparecer em nenhum arquivo do front-end nem no repositório, e o webhook precisa recusar assinatura inválida.