Pagos con tarjeta
Acepta Visa, Mastercard, American Express, Elo, Hipercard y otras redes principales mediante una página de pago alojada por Liqfy: ningún dato de tarjeta pasa por tus servidores y 3D Secure se gestiona automáticamente.
#Resumen del flujo
1. POST /v1/payments ──────▶ Liqfy returns the payment: id, status WAITING_PAYMENT, cardRedirectUrl + checkoutUrl
2. Redirect (or open in iframe) the customer to cardRedirectUrl
3. Customer enters card details + completes 3DS challenge on Liqfy's secure page
4. Customer is bounced back to your returnUrl (success or failure)
5. Webhook charge.paid (or charge.failed) confirms the outcome — trust the webhook, not the redirect¿Por qué una redirección? Mantiene los datos del titular de la tarjeta completamente fuera de tu infraestructura. Tu aplicación —backend o frontend— nunca ve un PAN, CVV ni fecha de caducidad. El procesador de Liqfy gestiona 3DS, la tokenización y el análisis de fraude de nuestro lado.
#1. Crear el pago
Endpoint POST /v1/payments
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" }
}'Idempotencia. POST /v1/payments acepta la cabecera Idempotency-Key (preferida) o el campo del cuerpo idempotencyKey como alternativa cuando falta la cabecera; envía al menos uno. Si envías ambos, deben coincidir: los valores divergentes devuelven 400, y no enviar ninguno devuelve 400 en esta ruta. Se admite cualquier cadena no vacía (se recortan los espacios en blanco circundantes), con ámbito por comercio ((merchant, key)): repetir la misma clave y el mismo cuerpo devuelve la respuesta almacenada; la misma clave con un cuerpo diferente devuelve 409. La cabecera Idempotency-Replayed no se emite en esta ruta: no construyas lógica de reintentos basada en ella.
| Campo | Notas |
|---|---|
amount | Entero > 0, unidad mínima (céntimos). 8990 = € 89,90. |
currency | 3–8 caracteres, por defecto "BRL" (BRL, EUR, USD; otras divisas bajo petición). |
paymentMethods | ["CREDIT_CARD"] aquí. La API acepta PIX, CRYPTO, CREDIT_CARD, BOLETO, MBWAY, MULTIBANCO, IDEAL, BANCONTACT, SEPA, BIZUM, SWAP_NOW. Al menos un elemento. Obligatorio salvo que se envíe checkout (si no, 400 — paymentMethods is required unless a checkout template is provided). |
checkout | Slug opcional de plantilla de checkout (1–80). Con una plantilla puedes omitir paymentMethods: la plantilla y la divisa del cobro deciden los métodos, filtrados a los que realmente se pueden enrutar, y pasan a ser los paymentMethods del cobro. Slug desconocido → 404; servicio de plantillas inaccesible → 503. |
customerId | Cadena opcional. |
customerName / customerDocument / customerEmail / customerPhone | Campos planos del cliente. customerEmail debe ser una dirección de correo electrónico; customerPhone es de formato libre (se recomienda E.164). customerEmail es muy recomendable: recibos, señales de fraude, pruebas en disputas de contracargos. |
customer | { name?, document?, email?, phone? } anidado alternativo (email debe ser una dirección de correo electrónico); se aplana en los campos anteriores (prevalecen los campos planos). |
expectedPayerDocument | CPF/CNPJ esperado del pagador, solo dígitos; se convierte en el bloqueo de pagador cuando el proveedor lo aplica. Un cobro bloqueado también necesita el nombre del pagador (customerName); si no, PAYER_NAME_REQUIRED. |
returnUrl | Adónde enviar al cliente cuando termine (éxito o fallo). URL http(s) sin fragmento. Campo canónico. |
cancelUrl | Adónde enviar al cliente si abandona. Misma regla de URL; requiere returnUrl (si no, 400 — cancelUrl requires returnUrl). |
metadata | Clave/valor de formato libre. metadata.returnUrl / metadata.cancelUrl, si están presentes, deben ser URL http(s). metadata.returnUrl sigue controlando la redirección del checkout alojado, pero el campo canónico es returnUrl de nivel superior. |
items | { name, quantity, unitPrice, totalPrice }[] opcional; name no vacío, los números enteros > 0. |
webhookUrl | Sustitución opcional de la URL de webhook por transacción; debe ser una URL válida. |
idempotencyKey | Alternativa en el cuerpo para la cabecera Idempotency-Key (ver arriba). |
country | Opcional, 2–3 caracteres; dato de entrada para el enrutamiento de la cuenta del proveedor. |
riskTier | Opcional: LOW, MEDIUM o HIGH; dato de entrada para el enrutamiento de la cuenta del proveedor. |
environment | Opcional: SANDBOX, STAGING, PRODUCTION; permite dirigirte explícitamente a tu cuenta sandbox. Si se omite, se deriva del entorno del servidor. |
expiresIn | Tiempo de vida opcional del QR de Pix en segundos, 60–604800; se envía al proveedor como caducidad solicitada (prevalece la caducidad devuelta por el proveedor). |
Respuesta 201 Created: la respuesta de creación ya incluye los artefactos del método (aquí cardRedirectUrl) y checkoutUrl; no necesitas un GET posterior para verlos.
{
"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"
}checkoutUrl solo está presente en la creación y la consulta; las filas del listado y la respuesta de cancelación lo omiten.
#Errores en la creación
| HTTP | code | Cuándo |
|---|---|---|
400 | — (errores de validación aplanados) | Cuerpo no válido; clave de idempotencia ausente o divergencia entre cabecera y cuerpo; cancelUrl sin returnUrl; paymentMethods ausente sin checkout. |
401 | — | La solicitud no identifica tu cuenta (debe llegar autenticada a través del API gateway). |
403 | fraud.tenant_velocity_exceeded | Demasiados intentos de pago desde tu cuenta en un intervalo corto (por defecto: 200 cada 60 s). |
403 | fraud.customer_velocity_exceeded | Demasiados intentos de pago para el mismo customerDocument o customerEmail en un intervalo corto (por defecto: 10 cada 300 s). Los cobros caducados y cancelados no cuentan; los rechazados sí. Los cobros sin ninguno de los dos campos no están sujetos a esta regla. |
404 | — | Slug de checkout desconocido. |
4xx | NO_PROVIDER_CONFIGURED | No hay ningún proveedor de pago configurado para este método de pago y esta divisa. |
4xx | UNKNOWN_METHOD | Método de pago no admitido. |
4xx | MOCK_PROVIDER_IN_PRODUCTION | El método no está disponible para cobros reales. |
4xx | PAYER_IDENTITY_REQUIRED | El cobro requiere la identidad completa del pagador (nombre, correo electrónico, teléfono e identificación fiscal). |
4xx | PAYER_NAME_REQUIRED | expectedPayerDocument enviado sin el nombre del pagador. |
4xx | PAYMENT_REJECTED | El proveedor rechazó el cobro (también es el código por defecto para cualquier otro rechazo del lado del proveedor). |
503 | PROVIDER_UNAVAILABLE | El proveedor de pago no está disponible temporalmente: inténtalo de nuevo más tarde (mismo Idempotency-Key). |
5xx | PROVIDER_ERROR | El proveedor devolvió una respuesta inesperada. |
Los errores 4xx del lado del proveedor mantienen el estado HTTP que devolvió la capa de enrutamiento. Los mensajes de error son neutros por diseño: nunca nombran al proveedor ni sus códigos internos; indica el id de la solicitud al soporte si necesitas el detalle.
Con el SDK oficial (el mismo paquete neutro para todas las marcas; configúralo con https://api.liqfy.com.br):
| node | python | php | dotnet |
|---|---|---|---|
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) devuelve el sobre de página { data, total, page, limit }. Parámetros de consulta: page (por defecto 1), limit (por defecto 20, máx. 100), status, currency, startDate / endDate (fecha-hora ISO-8601), source (API, PANEL, MANUAL, INDEFINIDO), sortBy, sortOrder (asc / desc).
#2. Redirigir al cliente
cardRedirectUrl viene en la respuesta de creación (y en cada GET /v1/payments/{id}), así que puedes redirigir de inmediato:
curl https://api.liqfy.com.br/v1/payments/b2c3d4e5-f6a7-4890-9bcd-ef0123456789 \
-H "Authorization: Bearer $LIQFY_API_KEY"{
"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"
}Envía al cliente a cardRedirectUrl:
<a href="{{ cardRedirectUrl }}">Pay with card</a>O haz un 303 See Other del lado del servidor:
res.redirect(303, transaction.cardRedirectUrl);Cuando el cliente completa el flujo (o lo abandona), vuelve a la returnUrl que proporcionaste (el checkout alojado lee la misma redirección de metadata.returnUrl), con ?transactionId=...&status=... añadido. No confíes en esos parámetros de consulta. Son solo informativos: espera al webhook antes de completar el pedido.
#3. Confirmar mediante webhook
{
"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": {}
}
}
}El estado público del cobro es paid para los pagos con tarjeta correctos. El vocabulario de estados públicos es pending, processing, paid, failed, expired, cancelled, refunded, disputed.
Los rechazos llegan como charge.failed; para la caducidad se prefiere charge.expired. Lee data.object.status para conocer el resultado público. El evento no expone los motivos de rechazo del emisor.
#Ciclo de vida
WAITING_PAYMENT ──▶ PROCESSING ──▶ APPROVED ✓ fulfill order
──▶ REFUSED issuer declined
──▶ CANCELLED customer abandoned
──▶ EXPIRED redirect link aged out (24h)Después de la liquidación (de D+1 a D+30 según la red y tu contrato), también puedes ver:
APPROVED ──▶ CHARGEBACK issuer raised a chargeback
APPROVED ──▶ DISPUTE cardholder opened a dispute
APPROVED ──▶ REFUNDED you (or Liqfy) issued a refundCada uno dispara un webhook para que puedas sincronizar el estado del pedido.
#Otras operaciones sobre el pago
| Endpoint | Qué hace |
|---|---|
POST /v1/payments/{id}/cancel | Cancela el pago; lo devuelve con la misma forma que GET /v1/payments/{id} (sin checkoutUrl). |
POST /v1/payments/{id}/resend-webhook | Solicita a Liqfy que reenvíe el webhook del pago. |
POST /v1/payments/{id}/reconcile | Vuelve a comprobar el estado del pago con el proveedor; devuelve { reconciled, status }. |
GET /v1/payments/{id}/refunds | Historial de reembolsos del pago. failureReason es siempre null y reason solo se rellena para los reembolsos que solicitaste tú mismo. |
POST /v1/payments/{id}/confirm-sandbox | Solo sandbox: confirma un cobro simulado para que se dispare el webhook de finalización. Se rechaza para cualquier cobro que no sea de sandbox; nunca abona saldo en un monedero. |
POST /v1/payments/{id}/refund | No disponible para llamadas con clave de API: devuelve 403 REFUND_ADMIN_ONLY (ver FAQ). |
#Tarjetas de prueba
En sandbox, la página de redirección acepta estos números deterministas (cualquier fecha de caducidad futura, cualquier CVV):
| Número | Resultado |
|---|---|
4111 1111 1111 1111 | Siempre APPROVED |
4000 0000 0000 0002 | Siempre REFUSED — rechazo genérico |
4000 0000 0000 0069 | Siempre REFUSED — tarjeta caducada |
4000 0000 0000 9995 | Siempre REFUSED — fondos insuficientes |
4000 0000 0000 3220 | Activa un desafío 3DS y luego APPROVED |
Los cobros de sandbox se ejecutan en el simulador: nunca abonan saldo en tu monedero y se excluyen de las cifras de volumen, ingresos y métricas (aun así aparecen en los listados y en el detalle del cobro). Usa POST /v1/payments/{id}/confirm-sandbox para disparar el webhook de finalización de extremo a extremo con una clave de prueba.
Consulta sandbox para ver la lista completa.
#FAQ
P: ¿Puedo mantener al cliente en mi dominio? R: En la fase 2 lanzaremos un widget integrable (iframe + tokenizador) que conserva tu marca y, a la vez, mantiene los datos del titular de la tarjeta fuera de tu infraestructura. Por ahora: redirección alojada.
P: ¿Admiten tarjetas guardadas? R: Sí, pero requiere que tu lado cuente con su propia certificación de cumplimiento de seguridad de datos de tarjetas. Escribe a suporte@liqfy.com.br para habilitar la Vault API.
P: ¿Qué versión de 3DS se usa? R: 3DS 2.x con flujo sin fricción cuando el emisor lo permite. La autenticación reforzada de clientes (SCA) es obligatoria para EUR; se aplica del lado del servidor.
P: ¿Reembolsos?
R: Hoy los reembolsos se solicitan desde el panel de administración, totales o parciales; la API no expone un endpoint de reembolso utilizable por el comercio: POST /v1/payments/{id}/refund con una clave de API devuelve 403 REFUND_ADMIN_ONLY. Aun así, puedes leer el historial de reembolsos con GET /v1/payments/{id}/refunds.
P: A mi cliente se le cobró, pero nunca recibí un webhook.
R: Revisa GET /v1/webhooks/deliveries: probablemente lo intentamos y tu endpoint devolvió una respuesta distinta de 2xx. Reintentamos hasta 15 veces. Después, usa POST /v1/payments/{id}/resend-webhook o contacta con soporte para reenviarlo.