Kit de Integração v1

Documentação
LofyPay

Aprenda a integrar PIX em tempo real, saques automatizados e webhooks com nossa API REST. Tudo que você precisa pra começar a aceitar pagamentos em minutos.

Por que LofyPay
  • API REST
    JSON puro, sem SDK obrigatório
  • PIX em tempo real
    QR code retornado na hora
  • Webhooks
    Notificação automática on-paid
  • Ambiente seguro
    HMAC, rate limit, IP allowlist
Começar

Introdução

A API LofyPay é REST, retorna JSON, usa autenticação por chave no body e tem rate-limit por IP e por chave. Esta documentação cobre os 4 endpoints principais.

Você precisa de uma chave de API (gerada no painel) pra fazer qualquer requisição. As chaves têm prefixo sk_live_ (produção) ou sk_test_ (sandbox).

  • JSON puro — sem SDK obrigatório
  • Respostas em application/json
  • Rate limit: 30 req/min por IP, 120 req/min por API key (gateway)
  • CORS configurável por origem confiável
Começar

Ambientes

Use sk_test_ para testes e sk_live_ para produção. Ambas as chaves apontam para a mesma URL base — o roteamento para sandbox é feito pelo prefixo da chave.

Produção

LIVE

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

Base URL
https://app.lofypay.com/api/v1
prefix:sk_live_…

Sandbox

TEST

Para desenvolvimento e testes de integração. Nenhuma cobrança real é processada.

Base URL
https://app.lofypay.com/api/v1
prefix:sk_test_…
URL única
Não há domínio separado pra sandbox. A própria chave (sk_test_) sinaliza o ambiente.

Simular pagamento (sandbox)

No sandbox o PIX nunca é pago de verdade. Pra 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 pra notification_url. Só aceita chaves sk_test_ (chave live retorna 403) e nunca movimenta saldo real.

POST
https://app.lofypay.com/api/v1/sandbox/pay
{ }JSON
{
  "api-key": "sk_test_…",
  "idTransaction": "52fc5262-…",
  "status": "paid"
}

status aceita paid · failed · expired · pending. Também dá pra 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 shape próprio
Ele vai com o header X-Lofypay-Environment: test e o payload acima — sem amount/paid_at e com externalReference em camelCase. Use-o pra validar conectividade; o contrato de produção é o da seção Webhooks.
Começar

Autenticação

A LofyPay aceita a Secret Key (sk_live_… ou sk_test_…) em três lugares — escolha o que for mais cômodo. Nunca 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 (compat)
$_Exemplo (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 (body — compat)
{
  "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.
API

Gerar PIX

Cria uma cobrança PIX dinâmica e retorna o BR code (copia-e-cola) para exibir no checkout — algumas adquirentes também devolvem o QR Code em base64.

POST
https://app.lofypay.com/api/v1/gateway

Parâmetros do body

CampoTipoDescrição
api-keyobrigatório
stringSua chave (sk_live_… ou sk_test_…).
amountobrigatório
numberValor da cobrança em reais (R$). Ex: 100.50
method
enumDefault "pix".
clientobrigatório
objectDados do pagador. O objeto é obrigatório; campos ausentes recebem placeholders neutros.
client.name
stringNome completo do pagador. Recomendado — sem ele a cobrança é registrada como genérica.
client.document
stringCPF ou CNPJ do pagador (só dígitos). Recomendado para conciliação e defesa de MED.
client.email
stringE-mail do pagador. Recomendado para conciliação.
external_reference
stringID do pedido no seu sistema (ex: PEDIDO-1050). Volta no webhook.
notification_url
urlURL (HTTPS recomendado) que recebe um POST automático quando o pagamento confirmar.
expiration
numberValidade da cobrança em segundos (60–86400).
split.percentual
numberPercentual do bruto a destinar pra outro user (0–100).
split.destino
stringuser_id do recebedor do split.
metadata.order_id
stringAlternativa a external_reference.

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"
    }
  }'
JSNode.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 (copia-e-cola). 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 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, 120 por minuto por API key. Excedeu retorna HTTP 429 com header Retry-After em segundos.
API

Consultar status

Verifica se um pagamento foi aprovado usando o idTransaction retornado na criação. Útil pra polling — mas prefira webhooks quando possível.

POST
https://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 e a LofyPay te avisa exatamente quando o pagamento confirmar — latência tipicamente abaixo de 2 segundos.
API

Webhooks

Dois canais: o 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 pro seu servidor assim que o pagamento confirma. Esse canal envia apenas Content-Type: application/jsonnão é assinado.

Payload recebido

{ }JSON
{
  "status": "PAID",
  "idTransaction": "52fc5262-4063-4900-933b-55e6985096a1",
  "amount": 100.50,
  "external_reference": "PEDIDO-123456",
  "paid_at": "2026-05-20 14:30:12"
}
Canal sem assinatura — sempre concilie
Esse POST não carrega assinatura — qualquer um pode enviar um corpo idêntico pro 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 recebimento com HTTP 200
Se sua resposta não for 2xx, a LofyPay tenta novamente com backoff crescente (1 min · 5 min · 30 min · 2 h · 12 h), até 5 tentativas. Responda 200 rápido e processe de forma assíncrona.

Canal 2 — Endpoints registrados & eventos

Além do 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 (HMAC-SHA256 em X-LofyPay-Signature) com o secret do endpoint — ideal para quem usa a LofyPay como adquirente e precisa repassar os eventos à própria base.

CampoTipoDescrição
payment.approved
eventoCobrança confirmada (PIX pago).
payment.refunded
eventoCobrança estornada/devolvida — o crédito foi revertido.
med.opened
eventoAbriu um MED (infração PIX / devolução BACEN) sobre uma transação sua.
med.updated
eventoMED mudou de status (ex: em análise, defesa enviada).
med.resolved
eventoMED encerrado. Campo resolution: lost (procedente) ou kept (improcedente).

Headers enviados (canal registrado)

CampoTipoDescrição
Content-Type
stringapplication/json
X-LofyPay-Event
stringTipo do evento (ex: payment.approved).
X-LofyPay-Timestamp
stringUnix (segundos) de quando o evento foi assinado.
X-LofyPay-Signature
stringAssinatura no formato t=<unix>,v1=<hmac>. O HMAC-SHA256 é calculado sobre <timestamp>.<body> com o secret do endpoint.
X-LofyPay-Id
stringUUID único da entrega. Use pra deduplicar (idempotência).
User-Agent
stringLofyPay-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. 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. Sempre rejeite também timestamps fora da janela (anti-replay).

Verificando a assinatura

JSJavaScript
import { createHmac, timingSafeEqual } from "node:crypto";

// secret = o secret do endpoint cadastrado em /gateway
function verifyLofypay(rawBody, secret, sigHeader, toleranceSec = 300) {
  // sigHeader = "t=1730475012,v1=9f3c…"
  const parts = Object.fromEntries(
    sigHeader.split(",").map((kv) => {
      const i = kv.indexOf("=");
      return [kv.slice(0, i).trim(), kv.slice(i + 1).trim()];
    })
  );
  const t = Number(parts.t);
  if (!Number.isFinite(t)) return false;

  // 1) Anti-replay: recusa eventos fora da janela (relógio do seu servidor).
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;

  // 2) Assinatura: HMAC de `${t}.${rawBody}` (use o corpo CRU, sem reparsear).
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1 || "", "hex");
  return a.length === b.length && timingSafeEqual(a, b); // timing-safe
}
Retentativas re-assinadas
Se sua resposta não for 2xx, o evento é reenviado com o mesmo backoff (até 5 tentativas). Cada retentativa é re-assinada com um timestamp novo, mas mantém o mesmo X-LofyPay-Id e o mesmo corpo. Responda 200 rápido e processe de forma assíncrona.
Idempotência por X-LofyPay-Id
A mesma entrega pode chegar mais de uma vez (retry). Guarde os X-LofyPay-Id já processados e ignore repetições antes de creditar/agir.
Camada extra: concilie pela API
Para o fluxo de pagamento, além da assinatura você pode confirmar de forma independente: consulte /api/v1/status com o idTransaction do evento e só libere se voltar PAID_OUT. É o equivalente — sem certificado — ao cross-check que a LofyPay faz com os adquirentes dela.

Exemplo — med.opened

{ }JSON
{
  "event": "med.opened",
  "sent_at": "2026-06-01T14:30:12.000Z",
  "data": {
    "med_case_id": 482,
    "provider": "onlyup",
    "provider_case_id": "infra_9f3c…",
    "type": "FRAUD",
    "reason": "ACCOUNT_HOLDER",
    "status": "pending_institution",
    "status_label": "Aguardando Instituição",
    "resolution": null,
    "amount": 150.00,
    "transaction": {
      "idTransaction": "52fc5262-4063-4900-933b-55e6985096a1",
      "external_reference": "PEDIDO-123456"
    },
    "opened_at": "2026-06-01T14:29:50.000Z",
    "due_at": "2026-06-06T14:29:50.000Z"
  }
}
Mesma verificação de assinatura
O corpo assinado é o JSON inteiro ({ event, sent_at, data }). Use a mesma função de X-LofyPay-Signature acima — o secret é o do endpoint cadastrado em /gateway. Em med.resolved, resolution: "lost" significa que a infração foi procedente e o valor foi devolvido ao pagador.
API

Cashout / Saque

Envia valores da sua carteira LofyPay pra uma chave PIX externa. Debita do saldo imediatamente; aprovação real depende da liquidação do adquirente.

POST
https://app.lofypay.com/api/v1/cashout

Alias legado: https://app.lofypay.com/api/c1/cashout continua funcionando, mas novos projetos devem usar /api/v1/cashout.

CampoTipoDescrição
api-keyobrigatório
stringSua Secret Key (também aceita Authorization: Bearer).
amountobrigatório
numberValor em reais. Mínimo R$ 1,00 (contas roteadas via CyberHub: R$ 15,00) — e o valor precisa cobrir a taxa total do saque.
nameobrigatório
stringNome do favorecido (titular da chave).
cpfobrigatório
stringCPF do favorecido (só dígitos).
keypixobrigatório
stringChave PIX de destino.
tipo_chaveobrigatório
enumTipo da chave: CPF · CNPJ · EMAIL · TELEFONE · EVP (aleatória).
{ }JSON
{
  "amount": 50.00,
  "name": "Favorecido da Silva",
  "cpf": "12345678900",
  "keypix": "favorecido@exemplo.com",
  "tipo_chave": "EMAIL"
}
$_cURL
curl -X POST https://app.lofypay.com/api/v1/cashout \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 50.00,
    "name": "Favorecido da Silva",
    "cpf": "12345678900",
    "keypix": "favorecido@exemplo.com",
    "tipo_chave": "EMAIL"
  }'

Resposta

{ }200 — saque aceito (em liquidação)
{
  "success": true,
  "code": "SUCCESS",
  "message": "…",
  "retryable": false,
  "status": "PROCESSING",
  "withdrawalId": 1234,
  "id": 1234
}
{ }422 — recusado pelo provedor
{
  "success": false,
  "code": "PROVIDER_REJECTED",
  "message": "…",
  "retryable": false,
  "status": "REJECTED",
  "id": 1234
}

Trate sucesso/erro pelo campo success e pelo HTTP status — não por body.status, que aqui carrega o status do saque (PROCESSING, PENDING_REVIEW, REJECTED…) e nunca vale "error". Códigos principais: SUCCESS (200), CASHOUT_PENDING_REVIEW (202 — fila de aprovação manual), MISSING_FIELDS / INVALID_AMOUNT (400), CASHOUT_IN_PROGRESS (409), INSUFFICIENT_BALANCE / PROVIDER_REJECTED (422), CASHOUT_COOLDOWN (429), PROVIDER_NOT_AVAILABLE (502).

Rate limit mais apertado
10 requisições por minuto por IP, 30 por minuto por API key. Saques têm validação extra e podem ficar em fila de aprovação manual.
API

Health Check

Verifica se a API e o banco estão respondendo. Use em status pages e monitoramento.

GET
https://app.lofypay.com/api/health
{ }JSON
{
  "ok": true
}

200 com { "ok": true } quando tudo está de pé; 503 com { "ok": false } quando o banco não responde. Detalhes de diagnóstico (db, latência) exigem token interno e não fazem parte da resposta pública.

JSJavaScript
const res = await fetch("https://app.lofypay.com/api/health");
const { ok } = await res.json();
console.log(ok ? "API Online" : "API Offline");
Referência

Códigos de erro

A API responde sempre com JSON, mas o envelope de erro varia por endpoint: o gateway usa { status: 'error', message }, o /status usa { error } e o cashout usa { success, code, message }.

CampoTipoDescrição
400
numberBody inválido, parâmetro faltando — ou transação não encontrada no /status ({ "error": "No matching record found" }).
401
numberChave de API ausente, inválida ou revogada.
403
numberIP não autorizado (allowlist) ou chave live em endpoint de sandbox.
404
numberTransação de teste não encontrada (em /sandbox/pay).
405
numberMétodo HTTP não permitido (use POST onde indicado).
409
numberJá existe um saque em andamento (CASHOUT_IN_PROGRESS).
422
numberSaldo insuficiente ou regra de negócio bloqueou a operação.
429
numberRate limit excedido. Veja header Retry-After.
500
numberErro interno. Tente novamente em alguns segundos.
{ }Envelope do gateway
{
  "status": "error",
  "message": "API key rate limit exceeded"
}
Referência

Segurança

Boas práticas pra manter sua integração robusta e suas chaves seguras.

Nunca exponha a sk_

Use variáveis de ambiente no servidor. Frontend só usa a pk_ (não dá pra criar cobrança com ela).

Use HTTPS nos webhooks

URLs http:// são aceitas, mas o payload trafega sem criptografia. Em produção, use sempre https://.

Verifique a assinatura

No canal de endpoints registrados, confirme X-LofyPay-Signature antes de processar. O notification_url não é assinado — concilie via /api/v1/status.

Habilite IP allowlist

Em /gateway, fixe os IPs do seu servidor. Requisições de fora retornam 403.

Idempotência por external_reference

Use um ID único do seu pedido. Webhooks duplicados ficam fáceis de filtrar no seu lado.

Regenere após vazamento

Se a sk_ apareceu em log, commit, screenshot ou e-mail — regenere imediatamente em /gateway.