Pular para o conteúdo
LIQFYdocs
PTENENEN
Ir para o painel

Pagamentos com cartão

Aceite Visa, Mastercard, American Express, Elo, Hipercard e as outras grandes bandeiras por uma página de pagamento hospedada pela Liqfy — nenhum dado de cartão passa pelos seus servidores, e o 3D Secure é tratado automaticamente.

#O fluxo, de relance

text
1. POST /v1/payments  ──────▶  a Liqfy devolve o pagamento: id, status WAITING_PAYMENT, cardRedirectUrl + checkoutUrl
2. Redirecione o cliente (ou abra num iframe) para o cardRedirectUrl
3. O cliente digita os dados do cartão e conclui o desafio 3DS na página segura da Liqfy
4. O cliente volta para o seu returnUrl (com sucesso ou com falha)
5. O webhook charge.paid (ou charge.failed) confirma o resultado — confie no webhook, não no redirect

Por que um redirect? Ele mantém os dados do portador do cartão totalmente fora da sua infraestrutura. A sua aplicação — backend ou frontend — nunca vê PAN, CVV nem validade. O processador da Liqfy cuida do 3DS, da tokenização e da análise antifraude do nosso lado.

#1. Criar a cobrança

Endpoint POST /v1/payments

bash
curl -X POST https://api.liqfy.com.br/v1/payments \
  -H "Authorization: Bearer $LIQFY_API_KEY" \
  -H "Idempotency-Key: ORD-9821" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 8990,
    "currency": "EUR",
    "paymentMethods": ["CREDIT_CARD"],
    "customerName": "João Costa",
    "customerEmail": "joao@example.com",
    "customerPhone": "+351912345678",
    "returnUrl": "https://shop.example.com/orders/9821/return",
    "cancelUrl": "https://shop.example.com/cart",
    "metadata": { "orderId": "ORD-9821" }
  }'

Idempotência. O POST /v1/payments aceita o cabeçalho Idempotency-Key (preferido) ou o campo de corpo idempotencyKey como alternativa quando o cabeçalho está ausente; envie pelo menos um. Se enviar os dois, eles precisam ser iguais — valores divergentes são 400, e nenhum dos dois também é 400 nesta rota. Qualquer string não vazia (espaços nas pontas são removidos), com escopo por lojista ((merchant, key)): repetir a mesma chave e o mesmo corpo devolve a resposta guardada; a mesma chave com corpo diferente é 409 (idempotency_key_reused). O cabeçalho Idempotency-Replayed não é emitido nesta rota — não monte lógica de retry em cima dele.

CampoObservação
amountInteiro > 0 na menor unidade (centavos). 8990 = € 89,90.
currency3–8 caracteres, padrão "BRL" (BRL, EUR, USD; outras moedas sob pedido).
paymentMethods["CREDIT_CARD"] aqui. A API aceita PIX, CRYPTO, CREDIT_CARD, BOLETO, MBWAY, MULTIBANCO, IDEAL, BANCONTACT, SEPA, BIZUM, SWAP_NOW. Pelo menos um item. Obrigatório, a menos que você envie checkout (senão 400 — paymentMethods is required unless a checkout template is provided).
checkoutSlug opcional de template de checkout (1–80). Com template, dá para omitir paymentMethods: o template e a moeda da cobrança decidem os métodos, filtrados pelo que é de fato roteável, e eles viram o paymentMethods da cobrança. Slug desconhecido → 404; serviço de templates fora do ar → 503.
customerIdString opcional.
customerName / customerDocument / customerEmail / customerPhoneCampos de cliente planos. customerEmail precisa ser um e-mail; customerPhone é texto livre (E.164 recomendado). customerEmail é muito recomendado — comprovante, sinais de fraude e prova na contestação de chargeback.
customerAlternativa aninhada { name?, document?, email?, phone? } (o email precisa ser um e-mail); é achatada nos campos acima (os campos planos vencem).
expectedPayerDocumentCPF/CNPJ esperado do pagador, só dígitos; vira a trava de pagador onde o provedor a aplica. Cobrança travada também exige o nome do pagador (customerName), senão PAYER_NAME_REQUIRED.
returnUrlPara onde mandar o cliente quando ele terminar (com sucesso ou com falha). URL http(s) sem fragmento. Campo canônico.
cancelUrlPara onde mandar o cliente se ele desistir. Mesma regra de URL; exige returnUrl (senão 400 — cancelUrl requires returnUrl).
metadataChave/valor livre. metadata.returnUrl / metadata.cancelUrl, se existirem, precisam ser URLs http(s). O metadata.returnUrl ainda direciona o redirect do checkout hospedado, mas o campo canônico é o returnUrl de primeiro nível.
itemsOpcional { name, quantity, unitPrice, totalPrice }[]; name não vazio, os números inteiros > 0.
webhookUrlOverride opcional de webhook por transação; precisa ser uma URL válida.
idempotencyKeyAlternativa de corpo para o cabeçalho Idempotency-Key (acima).
countryOpcional, 2–3 caracteres; entrada de roteamento de conta do provedor.
riskTierOpcional LOW, MEDIUM ou HIGH; entrada de roteamento de conta do provedor.
environmentOpcional SANDBOX, STAGING, PRODUCTION — permite mirar explicitamente a sua conta de sandbox. Se omitido, é derivado do ambiente do servidor.
expiresInOpcional, tempo de vida do QR Pix em segundos, 60–604800; repassado ao provedor como expiração pedida (a expiração devolvida pelo provedor vence).

Resposta 201 Created — a resposta de criação já traz os artefatos do método (aqui o cardRedirectUrl) e o checkoutUrl; não é preciso um GET depois para vê-los.

json
{
  "id": "b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
  "status": "WAITING_PAYMENT",
  "amount": 8990,
  "currency": "EUR",
  "paymentMethods": ["CREDIT_CARD"],
  "customerName": "João Costa",
  "customerEmail": "joao@example.com",
  "customerPhone": "+351912345678",
  "cardRedirectUrl": "https://checkout.liqfy.com.br/c/b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
  "checkoutUrl": "https://checkout.liqfy.com.br/b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
  "createdAt": "2026-04-25T15:50:11.000Z"
}

O checkoutUrl só existe na criação e na consulta — nas linhas da listagem e na resposta do cancelamento ele não vem.

#Erros na criação

HTTPcodeQuando
400— (erros de validação achatados)Corpo inválido; chave de idempotência ausente ou divergente entre cabeçalho e corpo; cancelUrl sem returnUrl; paymentMethods ausente sem checkout.
401—A requisição não identifica a sua conta (ela precisa chegar autenticada pelo gateway da API).
403fraud.tenant_velocity_exceededTentativas de pagamento demais da sua conta numa janela curta (padrão: 200 a cada 60 s).
403fraud.customer_velocity_exceededTentativas de pagamento demais para o mesmo customerDocument ou customerEmail numa janela curta (padrão: 10 a cada 300 s). Cobranças expiradas e canceladas não contam; recusadas contam. Cobranças sem nenhum dos dois campos não entram nessa regra.
404—Slug de checkout desconhecido.
4xxNO_PROVIDER_CONFIGUREDNenhum provedor configurado para este método de pagamento e moeda.
4xxUNKNOWN_METHODMétodo de pagamento não suportado.
4xxMOCK_PROVIDER_IN_PRODUCTIONO método não está disponível para cobranças reais.
4xxPAYER_IDENTITY_REQUIREDA cobrança exige a identidade completa do pagador (nome, e-mail, telefone e documento).
4xxPAYER_NAME_REQUIREDexpectedPayerDocument enviado sem o nome do pagador.
4xxPAYMENT_REJECTEDO provedor recusou a cobrança (também é o código usado para qualquer outra recusa do lado do provedor).
503PROVIDER_UNAVAILABLEO provedor está temporariamente indisponível — tente de novo mais tarde (mesma Idempotency-Key).
5xxPROVIDER_ERRORO provedor devolveu uma resposta inesperada.

Os erros 4xx do lado do provedor mantêm o status HTTP devolvido pela camada de roteamento. As mensagens são neutras de propósito — nunca citam o provedor nem os códigos internos dele; informe o request id ao suporte se precisar do detalhe.

Com o SDK oficial (o mesmo pacote neutro para qualquer marca — configure-o com https://api.liqfy.com.br):

nodepythonphpdotnet
client.payments.create(input, { idempotencyKey })client.payments.create(input, idempotency_key)$client->payments->create($input, $idempotencyKey)client.Payments.CreateAsync(input, idempotencyKey)
client.payments.retrieve(id)client.payments.retrieve(id)$client->payments->retrieve($id)client.Payments.RetrieveAsync(id)
client.payments.list(query)client.payments.list(**query)$client->payments->list($query)client.Payments.ListAsync(query)

payments.list (GET /v1/payments) devolve o envelope de página { data, total, page, limit }. Parâmetros de query: page (padrão 1), limit (padrão 20, máximo 100), status, currency, startDate / endDate (data-hora ISO-8601), source (API, PANEL, MANUAL, INDEFINIDO), sortBy, sortOrder (asc / desc).

#2. Redirecionar o cliente

O cardRedirectUrl vem na resposta de criação (e em todo GET /v1/payments/{id}), então dá para redirecionar na hora:

bash
curl https://api.liqfy.com.br/v1/payments/b2c3d4e5-f6a7-4890-9bcd-ef0123456789 \
  -H "Authorization: Bearer $LIQFY_API_KEY"
json
{
  "id": "b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
  "status": "WAITING_PAYMENT",
  "amount": 8990,
  "currency": "EUR",
  "cardRedirectUrl": "https://checkout.liqfy.com.br/c/b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
  "createdAt": "2026-04-25T15:50:11.000Z"
}

Mande o cliente para o cardRedirectUrl:

html
<a href="{{ cardRedirectUrl }}">Pagar com cartão</a>

Ou faça um 303 See Other no servidor:

js
res.redirect(303, transaction.cardRedirectUrl);

Quando o cliente conclui o fluxo (ou desiste), ele volta para o returnUrl que você informou (o checkout hospedado lê o mesmo redirect em metadata.returnUrl) — com ?transactionId=...&status=... no final. Não confie nesses parâmetros de query. Eles são só informativos — espere o webhook antes de liberar o pedido.

#3. Confirmar pelo webhook

json
{
  "id": "evt_00000000000000000000000000000001",
  "object": "event",
  "api_version": "2026-07-23",
  "type": "charge.paid",
  "created_at": "2026-04-25T15:52:34.000Z",
  "data": {
    "object": {
      "id": "ch_b2c3d4e5-f6a7-4890-9bcd-ef0123456789",
      "object": "charge",
      "status": "paid",
      "amount": 8990,
      "currency": "EUR",
      "payment_method": "card",
      "settlement": {}
    }
  }
}

O status público da cobrança é paid para pagamentos com cartão aprovados. O vocabulário público de status é pending, processing, paid, failed, expired, cancelled, refunded, disputed.

Recusas chegam como charge.failed; expiração usa preferencialmente charge.expired. Leia data.object.status para o resultado público. O evento não expõe os motivos de recusa do emissor.

#Ciclo de vida

text
WAITING_PAYMENT  ──▶  PROCESSING  ──▶  APPROVED    ✓ libere o pedido
                                  ──▶  REFUSED     o emissor recusou
                                  ──▶  CANCELLED   o cliente desistiu
                                  ──▶  EXPIRED     o link de redirect venceu (24h)

Depois da liquidação (D+1 a D+30, conforme a bandeira e o seu contrato), você também pode ver:

text
APPROVED  ──▶  CHARGEBACK   o emissor abriu um chargeback
APPROVED  ──▶  DISPUTE      o portador do cartão abriu uma disputa
APPROVED  ──▶  REFUNDED     você (ou a Liqfy) fez um reembolso

Cada um dispara um webhook, para você sincronizar o estado do pedido.

#Outras operações sobre o pagamento

EndpointO que faz
POST /v1/payments/{id}/cancelCancela o pagamento; devolve-o no mesmo formato do GET /v1/payments/{id} (sem checkoutUrl).
POST /v1/payments/{id}/resend-webhookPede à Liqfy que reenvie o webhook do pagamento.
POST /v1/payments/{id}/reconcileReconsulta o status do pagamento no provedor; devolve { reconciled, status }.
GET /v1/payments/{id}/refundsHistórico de reembolsos do pagamento. failureReason vem sempre null e reason só vem preenchido nos reembolsos que você mesmo pediu.
POST /v1/payments/{id}/confirm-sandboxSó sandbox — confirma uma cobrança simulada para disparar o webhook de conclusão. Recusado para qualquer cobrança fora do sandbox; nunca credita carteira.
POST /v1/payments/{id}/refundIndisponível para chamadas com chave de API: devolve 403 REFUND_ADMIN_ONLY (veja as perguntas frequentes).

#Cartões de teste

No sandbox, a página de redirect aceita estes números determinísticos (qualquer validade futura, qualquer CVV):

NúmeroResultado
4111 1111 1111 1111Sempre APPROVED
4000 0000 0000 0002Sempre REFUSED — recusa genérica
4000 0000 0000 0069Sempre REFUSED — cartão vencido
4000 0000 0000 9995Sempre REFUSED — saldo insuficiente
4000 0000 0000 3220Dispara o desafio 3DS e depois APPROVED

Cobranças de sandbox rodam no simulador: nunca creditam a sua carteira e ficam fora dos números de volume, receita e métricas (continuam aparecendo nas listas e no detalhe da cobrança). Use POST /v1/payments/{id}/confirm-sandbox para disparar o webhook de conclusão de ponta a ponta com uma chave de teste.

Veja a lista completa em Sandbox.

#Perguntas frequentes

P: Dá para manter o cliente no meu domínio? R: Na fase 2 vamos lançar um widget incorporável (iframe + tokenizador) que mantém a sua marca e continua deixando os dados do portador do cartão fora da sua infraestrutura. Por enquanto: redirect hospedado.

P: Vocês aceitam cartão salvo? R: Sim, mas isso exige que o seu lado tenha a própria certificação de conformidade de segurança de dados de cartão. Escreva para suporte@liqfy.com.br para habilitar a Vault API.

P: Qual versão do 3DS é usada? R: 3DS 2.x, com fluxo sem fricção quando o emissor permite. A Autenticação Forte do Cliente (SCA) é obrigatória em EUR — isso é imposto no servidor.

P: E reembolso? R: Reembolso hoje é solicitado pelo painel administrativo, total ou parcial — a API não expõe um endpoint de reembolso utilizável pelo lojista: POST /v1/payments/{id}/refund com chave de API devolve 403 REFUND_ADMIN_ONLY. O histórico de reembolsos continua disponível em GET /v1/payments/{id}/refunds.

P: Meu cliente foi cobrado, mas o webhook nunca chegou. R: Confira GET /v1/webhooks/deliveries — provavelmente tentamos e o seu endpoint respondeu algo fora de 2xx. Fazemos até 15 retentativas. Depois disso, use POST /v1/payments/{id}/resend-webhook ou fale com o suporte para reenviar.

#Próximo

→ MB WAY · Multibanco · Referência da API