Skip to content
LIQFYdocs
PTENENEN
Go to dashboard

Pagos PIX

PIX es el sistema de pagos instantáneos de Brasil. Los fondos se liquidan en segundos, 24/7. Liqfy te ofrece un único endpoint que devuelve un BR Code (Pix Copia e Cola) y un artefacto de código QR listo para mostrar en tu checkout, en la misma respuesta que crea el cobro.

Consulta por número de pedido: envía ese número como Idempotency-Key al crear el cobro. Después usa GET /v1/charges/by-idempotency-key/{numero-de-pedido} con una clave API de la misma cuenta. metadata.order_id por sí solo no permite esta consulta.

El callback charge.paid puede incluir el E2E Pix en data.object.settlement.end_to_end_id una vez confirmado. El campo puede faltar si el E2E todavía no está disponible; consulta Webhooks.

#El flujo de un vistazo

text
1. POST /v1/charges  ────────▶  Liqfy returns a Charge: ch_…, status "pending", pix.br_code + QR
2. Render QR + Pix Copia e Cola in your UI
3. Customer scans / pastes in their bank app
4. Webhook charge.paid arrives at your endpoint
5. Mark order as paid

#1. Crear el cobro

Endpoint POST /v1/charges (atajo Pix-first: POST /v1/pix/charges fija payment_method: "pix", sobrescribiendo cualquier valor que envíes, y por lo demás se comporta exactamente igual que POST /v1/charges: mismos encabezados, misma idempotencia, mismo manejo de claves de prueba/producción y exactamente el mismo Charge en la respuesta)

Encabezados

text
Authorization: Bearer lq_live_…
Idempotency-Key: <unique per charge you intend to create>
Content-Type: application/json

Authorization: Bearer <key> es la forma canónica de presentar la clave de API. Idempotency-Key es obligatorio en POST /v1/charges (400 sin él): cualquier cadena no vacía, con alcance por comercio ((merchant, key)); repetir la misma clave y el mismo cuerpo devuelve el cobro original. Cuando la respuesta es una repetición, también incluye el encabezado Idempotency-Replayed: true, para que puedas distinguir un cobro repetido de uno recién creado.

Un cuerpo que no supera la validación se rechaza con 400 y los detalles de la validación.

Cuerpo

CampoTipoObligatorioNotas
amountintegersíCentavos. 1000 = R$ 10,00. Debe ser un entero positivo.
currencystringnoValor predeterminado "BRL", la única moneda que aceptan los cobros Pix.
payment_methodstringsí"pix" (el único valor aceptado hoy; el atajo /v1/pix/charges lo completa por ti).
descriptionstringnoHasta 500 caracteres. Se devuelve dentro de metadata.description.
expires_inintegernoSegundos hasta que el cobro expira (el tiempo de vida del QR Pix). 60–604800 (hasta 7 días). Un valor fuera del rango se rechaza con 400; nunca se recorta. Omítelo para usar la ventana predeterminada de la cuenta (normalmente 3600 = 1 h). pix.expires_at en la respuesta es siempre la expiración autoritativa.
customer.namestringnoSe muestra en el extracto bancario cuando es compatible.
customer.documentstringnoCPF o CNPJ.
customer.emailstringnoSe usa para los comprobantes.
customer.phonestringno
expected_payer_tax_idstringno⚠️ BLOQUEA el cobro a este CPF/CNPJ: con el bloqueo activo en tu cuenta, solo este documento puede pagar y un pago desde cualquier otro es rechazado (o devuelto, según el proveedor). No lo envíes si el pagador puede ser otra persona. Sin el bloqueo configurado en tu cuenta, el campo se ignora y el cobro se crea normalmente. Consulta Bloqueo por CPF del pagador más abajo. Requiere customer.name. Debe ser un CPF/CNPJ válido y, si también envías customer.document, debe coincidir con él.
metadataobjectnoPares clave/valor libres, devueltos tal cual (se eliminan las claves reservadas o con prefijo de guion bajo).

Ejemplo

bash
curl -X POST https://api.liqfy.com.br/v1/charges \
  -H "Authorization: Bearer $LIQFY_API_KEY" \
  -H "Idempotency-Key: ORD-7821" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 24900,
    "currency": "BRL",
    "payment_method": "pix",
    "description": "Pedido #7821",
    "expires_in": 1800,
    "customer": { "name": "Maria Silva", "document": "12345678901" },
    "metadata": { "order_id": "ORD-7821", "sku": "premium-monthly" }
  }'

Respuesta 201 Created

json
{
  "id": "ch_a1b2c3d4-0000-0000-0000-000000000009",
  "object": "charge",
  "amount": 24900,
  "currency": "BRL",
  "status": "pending",
  "payment_method": "pix",
  "customer": { "name": "Maria Silva", "document": "12345678901" },
  "pix": {
    "br_code": "000201BRCODEPIX",
    "qr_code_url": "https://qr.example/img.png",
    "expires_at": "2026-07-23T15:00:00.000Z"
  },
  "checkout_url": "https://checkout.liqfy.com.br/a1b2c3d4-0000-0000-0000-000000000009",
  "settlement": {},
  "metadata": { "order_id": "ORD-7821", "sku": "premium-monthly", "description": "Pedido #7821" },
  "created_at": "2026-07-23T14:30:00.000Z"
}
CampoQué hacer con él
pix.br_codeEl payload EMV "Pix Copia e Cola". Muéstralo en un botón de copiar al portapapeles.
pix.qr_code_url / pix.qr_code_base64La imagen QR, cuando el proveedor la devuelve: qr_code_url para una URL https:// (colócala en <img src="...">); de lo contrario, qr_code_base64 (un PNG que hay que decodificar). Un proveedor que devuelve solo br_code no genera ninguna de las dos: muestra siempre br_code como alternativa.
checkout_urlEl checkout alojado de Liqfy para este cobro: una página de pago lista para usar (QR, copiar y pegar, estado en tiempo real). Redirige al pagador aquí en lugar de construir tu propia pantalla. También lo devuelve GET /v1/charges/{id}, así que puedes obtenerlo más tarde a partir de un id almacenado. No puedes construir esta URL por tu cuenta: el checkout espera el id sin el prefijo ch_.
pix.expires_atCuándo expira este cobro. Muestra una cuenta regresiva; después de esto, status pasa a expired.
pix.txidTXID del BACEN: opcional, presente solo cuando el proveedor vinculó uno. Nunca lo trates como el id del cobro.
settlementSiempre presente. settlement.end_to_end_id suele estar vacío hasta la liquidación: aparece solo después de que el cobro llega a paid y ha sido liquidado/conciliado.

El objeto pix es { br_code?, qr_code_url?, qr_code_base64?, expires_at?, txid? } y está ausente cuando no hay br_code, imagen QR ni expiración que mostrar.

#Estados

EstadoSignificado
pendingCreado; esperando al pagador.
processingPago detectado; aún no confirmado (p. ej., retenido para revisión).
paidPagado y acreditado.
failedPago rechazado.
expiredexpires_at transcurrió sin pago.
cancelledCancelado antes del pago.
refundedDevuelto al pagador.
disputedContracargo o disputa.

#TXID y end_to_end_id

ch_… es siempre el id de cobro de Liqfy: el que almacenas, consultas y usas para conciliar los webhooks. pix.txid y settlement.end_to_end_id son datos contextuales de Pix definidos por el BACEN, que se exponen solo cuando corresponde:

json
{
  "id": "ch_a1b2c3d4-0000-0000-0000-000000000009",
  "pix": { "txid": "BACEN-TXID-77" },
  "settlement": {}
}

charge.id !== charge.pix.txid, siempre, incluso cuando ambos están presentes. Nunca uses el TXID ni el end_to_end_id como clave en tu base de datos; usa ch_….

#Bloqueo por CPF del pagador

Define expected_payer_tax_id para exigir que solo un CPF/CNPJ específico pague un cobro; por ejemplo, para garantizar que el comprador pague desde su propia cuenta y no desde la de un tercero.

⚠️ Enviar este campo BLOQUEA el cobro. Con el bloqueo activo en tu cuenta, solo el CPF/CNPJ indicado puede pagar: a quien pague desde otra cuenta el banco le rechaza el pago (o se le devuelve, según el proveedor). Envíalo solo cuando quieras esa restricción, p. ej., una apuesta o un depósito que debe provenir de la propia cuenta del cliente. Si el pagador puede ser otra persona (un padre que paga por un hijo, una empresa que paga por un socio), no lo envíes: usa solo customer.document, que es informativo y no bloquea nada.

Disponibilidad. El bloqueo está desactivado de forma predeterminada: depende del proveedor que atiende tu cuenta y solo se aplica donde soporte lo ha configurado. En una cuenta sin el bloqueo configurado, un cobro que incluya expected_payer_tax_id se crea normalmente, sin el bloqueo: cualquier pagador puede pagar y el pago se acredita. Puedes saberlo por la respuesta: el cobro no incluye expected_payer_tax_id. Cuando el bloqueo se aplica, el campo se devuelve en el cobro y rigen las reglas siguientes. ¿Quieres el bloqueo? Habla con soporte.

bash
curl -X POST https://api.liqfy.com.br/v1/charges \
  -H "Authorization: Bearer $LIQFY_API_KEY" \
  -H "Idempotency-Key: ORD-7821" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 24900,
    "payment_method": "pix",
    "customer": { "name": "Maria Silva", "document": "12345678901" },
    "expected_payer_tax_id": "12345678901"
  }'

Cómo funciona:

  • El cobro se crea y se paga exactamente como cualquier otro cobro Pix.
  • En la liquidación, Liqfy lee del banco el documento del pagador real y lo compara con expected_payer_tax_id.
  • Coincide → el cobro pasa a paid y se acredita tu saldo, como de costumbre.
  • Pagó otra persona → depende de cómo se aplique el bloqueo en el proveedor que atiende tu cuenta:
    • Detectivo (fail-closed): el cobro NO se acredita. Liqfy devuelve automáticamente el dinero a quien pagó (una devolução Pix estándar, referida al end_to_end_id original); el cobro permanece en processing hasta que esa devolución se liquida, y solo entonces pasa a refunded. Nunca recibes fondos de terceros.
    • Preventivo: se espera que la regla del lado del banco rechace al otro pagador antes de que el pago llegue. Si aun así un tercero logra pagar (la regla no funcionó), los fondos se acreditan de todos modos y el cobro permanece en paid, marcado como discrepancia de pagador: el dinero es tuyo.
  • El banco no informó el documento del pagador → el cobro se retiene en processing (nunca se acredita) para revisión manual, en lugar de acreditar a un pagador no verificable.

Notas:

  • La garantía de captura y devolución se limita a la vía detectiva (fail-closed): el cobro permanece en processing hasta que la devolución automática (devolução) se liquida y solo entonces pasa a refunded, y tu saldo de comercio nunca se acredita por el pagador que no coincide. En esa vía la verificación ocurre en la liquidación, no es un bloqueo a nivel bancario. En la vía preventiva, un pago que supera la regla del lado del banco se acredita de todos modos y el cobro permanece en paid (marcado como discrepancia de pagador). El BACEN permite que cualquier pagador escanee el QR en ambos modos, así que la app del pagador puede seguir mostrando el cobro.
  • expected_payer_tax_id requiere customer.name (necesario en el cobro y para la devolución), debe ser un CPF (11 dígitos) o CNPJ (14 dígitos) con dígito verificador válido y, si también envías customer.document, debe coincidir con él. De lo contrario, la solicitud se rechaza 400.
  • El valor se devuelve en el cobro (expected_payer_tax_id); el documento del pagador real nunca se expone.

#2. Consultar el cobro

Endpoint GET /v1/charges/{id}

bash
curl https://api.liqfy.com.br/v1/charges/ch_a1b2c3d4-0000-0000-0000-000000000009 \
  -H "Authorization: Bearer $LIQFY_API_KEY"

Acepta el id con prefijo ch_… o el id sin prefijo. Una vez que el Pix se ha pagado y liquidado, aparece settlement.end_to_end_id y status pasa a paid:

json
{
  "id": "ch_a1b2c3d4-0000-0000-0000-000000000009",
  "object": "charge",
  "status": "paid",
  "amount": 24900,
  "currency": "BRL",
  "payment_method": "pix",
  "customer": { "name": "Maria Silva", "document": "12345678901" },
  "settlement": { "end_to_end_id": "E12345678202610281433Zx9Yw8Vu7Ts" },
  "metadata": { "order_id": "ORD-7821" },
  "created_at": "2026-07-23T14:30:00.000Z"
}

#Consultar por Idempotency-Key

Endpoint GET /v1/charges/by-idempotency-key/{key}

Devuelve el mismo Charge público que GET /v1/charges/{id}, buscado por la Idempotency-Key que enviaste al crearlo, dentro de tu cuenta. Útil cuando la solicitud de creación agotó el tiempo de espera y nunca recibiste el id ch_….

bash
curl https://api.liqfy.com.br/v1/charges/by-idempotency-key/ORD-7821 \
  -H "Authorization: Bearer $LIQFY_API_KEY"

Se eliminan los espacios de la clave; una clave vacía o de más de 255 caracteres se rechaza con 400 (Invalid idempotency key).

#Polling vs. webhooks

Confía exclusivamente en los webhooks para el cumplimiento de pedidos y las escrituras en la base de datos: los webhooks son la fuente de verdad. GET /v1/charges/{id} sirve para consultas de estado bajo demanda (herramientas de soporte, un botón manual de "consultar estado"), no para un bucle de polling.

#3. Listar cobros

Endpoint GET /v1/charges — paginación por cursor (nunca por offset: no existe page/offset).

QueryTipoPredeterminadoNotas
limitint251–100.
starting_afterstring—Cursor: el id ch_… (o id sin prefijo) del último cobro que viste.
statusstring—Uno de los estados anteriores.
created_after / created_beforeISO-8601 datetime—UTC estricto (…Z); se rechazan los desfases horarios.
customer_idstring—Filtra por el documento del pagador.

Una query no válida se rechaza con 400 y los detalles de la validación.

bash
curl "https://api.liqfy.com.br/v1/charges?limit=25&status=paid" \
  -H "Authorization: Bearer $LIQFY_API_KEY"

Respuesta 200 OK — el sobre de cursor:

json
{
  "object": "list",
  "data": [ { "id": "ch_a1b2c3d4-0000-0000-0000-000000000009", "object": "charge", "status": "paid" } ],
  "has_more": true,
  "next_cursor": "ch_a1b2c3d4-0000-0000-0000-000000000009"
}

Recorre las páginas pasando next_cursor como starting_after hasta que has_more sea false. El orden es del más reciente al más antiguo.

#4. Recibir el webhook

Cuando el pagador paga, recibes un POST firmado en tu endpoint de webhook registrado con el sobre de evento canónico:

json
{
  "id": "evt_5f3a9b2c1d4e5f6a7b8c9d0e1f2a3b4c",
  "object": "event",
  "api_version": "2026-07-23",
  "type": "charge.paid",
  "created_at": "2026-07-23T14:31:00.000Z",
  "data": {
    "object": {
      "id": "ch_a1b2c3d4-0000-0000-0000-000000000009",
      "object": "charge",
      "amount": 24900,
      "currency": "BRL",
      "status": "paid",
      "payment_method": "pix",
      "settlement": { "end_to_end_id": "E12345678202610281433Zx9Yw8Vu7Ts" }
    }
  }
}

Consulta Webhooks para la verificación de firmas, la semántica de reintentos y el registro.

#Ejemplos de código

Con el SDK oficial (el mismo paquete neutral para todas las marcas; configúralo con la URL base de la API de esta página, https://api.liqfy.com.br):

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

charges.list devuelve el sobre de cursor { object: "list", data, has_more, next_cursor }. A continuación, los equivalentes en HTTP directo.

#Node.js (fetch)

js
const res = await fetch('https://api.liqfy.com.br/v1/charges', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.LIQFY_API_KEY}`,
    'Idempotency-Key': order.id,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 24900,
    currency: 'BRL',
    payment_method: 'pix',
    customer: { name: order.customer.name, document: order.customer.document },
    metadata: { order_id: order.id },
  }),
});

if (!res.ok) throw new Error(`Liqfy ${res.status}: ${await res.text()}`);
const charge = await res.json();
// charge.id (ch_…) → store on the order; charge.pix.br_code / qr_code_url → render immediately

#PHP

php
$ch = curl_init('https://api.liqfy.com.br/v1/charges');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST           => true,
  CURLOPT_HTTPHEADER     => [
    'Authorization: Bearer ' . getenv('LIQFY_API_KEY'),
    'Idempotency-Key: ' . $order->id,
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'amount'         => 24900,
    'currency'       => 'BRL',
    'payment_method' => 'pix',
    'customer'       => ['name' => $order->customerName],
    'metadata'       => ['order_id' => $order->id],
  ]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 201) throw new Exception("Liqfy $status: $body");
$charge = json_decode($body, true);

#Python (requests)

python
import os, requests

r = requests.post(
    "https://api.liqfy.com.br/v1/charges",
    headers={
        "Authorization": "Bearer " + os.environ['LIQFY_API_KEY'],
        "Idempotency-Key": order.id,
    },
    json={
        "amount": 24900,
        "currency": "BRL",
        "payment_method": "pix",
        "customer": {"name": order.customer.name},
        "metadata": {"order_id": order.id},
    },
    timeout=10,
)
r.raise_for_status()
charge = r.json()

#Casos límite y preguntas frecuentes

P: Faltan los campos pix.br_code / QR en la respuesta. R: La creación Pix-first los devuelve de forma síncrona. Si están ausentes, status normalmente ya mostrará failed; revisa primero si la solicitud tiene un error de validación (GET /v1/charges/{id} para volver a leer el cobro almacenado).

P: Mi solicitud de creación agotó el tiempo de espera, ¿se creó el cobro? R: Reintenta con la misma Idempotency-Key y el mismo cuerpo: si el cobro ya existe, recibes el original (con Idempotency-Replayed: true). También puedes buscarlo con GET /v1/charges/by-idempotency-key/{key}.

P: ¿Cuánto tiempo es válido el QR? R: Consulta pix.expires_at en el cobro; se define por cobro. Tras la expiración, status pasa a expired y hay que crear un nuevo cobro.

P: ¿Puedo reembolsar un cobro PIX? R: Hoy los reembolsos se solicitan desde el panel de administración; la API no expone un endpoint de reembolso utilizable por el comercio (rechaza a quienes llaman con clave de API). Lo mismo ocurre con /v1/charges/{id}/refund, que no existe.

P: ¿Se me cobrarán comisiones si el cliente nunca paga? R: No. Las comisiones se aplican solo a los cobros paid y nunca se detallan en la respuesta pública de charge (consulta Convenciones de la API §3).

P: Mi cliente pagó un importe incorrecto. R: PIX exige coincidencia exacta. Los cobros pagados de menos permanecen en pending y el banco reembolsa al pagador. El pago en exceso es poco común, pero se trata de la misma forma.

#Siguiente

→ Configurar webhooks para producción

Consulta identificadores de liquidación y consulta para las garantías E2E y la retención.