Saltar al contenido
Developer Docs

Implementación PagaFactu

Diagrama, llamadas paso a paso y ejemplos en múltiples lenguajes.

Diagrama de secuencia

Crear un cobro

Una sola llamada multipart/form-data. Recibes la URL de pago y el PDF.

Endpoint:

MétodoURL
POSThttps://nc-api-sandbox.zertiban.com/pagafactu/v1/flows/pagafactu

Headers:

HeaderValor
AuthorizationBearer {access_token}
x-tenant-id{businessUuid}

Sustituye los valores entre llaves {} por los tuyos:

shell
curl -X POST https://nc-api-sandbox.zertiban.com/pagafactu/v1/flows/pagafactu \
  -H "Authorization: Bearer {access_token}" \
  -H "x-tenant-id: {businessUuid}" \
  -F 'payload={
    "externalId": "FACTURA-2026-001",
    "countryCode": "ES",
    "operations": [{
      "externalId": "OP-2026-001",
      "configuration": "{configurationUuid}",
      "payment": {
        "amount": 15075,
        "currency": "EUR",
        "types": ["PSD2_PAYMENT"],
        "psd2Payment": {
          "type": "SINGLE_PAYMENT",
          "product": "SEPA_CREDIT_TRANSFER",
          "creditorAccount": { "uuid": "{creditorAccountUuid}" }
        },
        "concept": "Factura 2026-001",
        "debtor": {
          "name": "Ana",
          "lastName": "Martinez",
          "email": "[email protected]"
        }
      },
      "invoice": {
        "externalId": "INV-2026-001",
        "name": "Factura 2026-001",
        "generateDocument": true
      }
    }]
  };type=application/json'

amount va en céntimos: 15075 = 150,75 EUR. externalId es tu identificador interno: aparece en los webhooks y en las consultas, lo que te permite reconciliar sin guardar los UUIDs de Zertiban.

Respuesta (201 Created):

json
{
  "uuid": "28ffd216-5ee8-4e99-b1a9-511961e9c655",
  "createdAt": "2026-04-01T09:15:00Z",
  "operations": [
    {
      "uuid": "b7aa09fe-5678-1234-90ab-cdef98765432",
      "url": "https://nc-api-sandbox.zertiban.com/link-validator/v1/links/eyJhbGci...",
      "document": "JVBERi0xLjQK..."
    }
  ]
}

Guarda en tu base de datos:

CampoPara qué
operations[0].uuidConsultas de estado y reconciliación con webhooks
operations[0].urlEnlace de pago que envías al cliente
operations[0].documentPDF en Base64 (solo presente si generateDocument: true). Decodifica: echo "JVBERi0..." | base64 -d > factura.pdf

Variantes de creación

1. PDF con QR generado por Zertiban (caso por defecto)

Es el escenario del ejemplo de arriba: "generateDocument": true con un name. Zertiban genera un PDF nuevo con el QR de cobro y lo devuelve en operations[0].document (Base64).

2. Sin documento

Si no necesitas documento, usa "generateDocument": false:

json
"invoice": {
  "externalId": "INV-2026-001",
  "generateDocument": false
}

La respuesta no incluirá el campo document. El campo name de invoice no es necesario.

3. Tu propio PDF + página añadida por Zertiban

Si quieres adjuntar tu propio PDF, mantén "generateDocument": true, añade "id": "mi-factura" en invoice y sube el fichero como segundo part multipart. El nombre del part debe coincidir exactamente con el valor de invoice.id:

shell
-F '[email protected];type=application/pdf'

Zertiban añade una página PagaFactu al final de tu PDF (no lo reemplaza) con:

  • QR de cobro
  • Enlace directo a la operación

Tamaño máximo del fichero PDF: 8 MB.

Si combinas generateDocument: false con un fichero adjunto, el fichero se ignora silenciosamente y no se devuelve ningún documento.

4. Cobro programado

Si quieres que el pago se ejecute en una fecha futura, cambia psd2Payment.type a "FUTURE_PAYMENT" y añade la fecha:

json
"psd2Payment": {
  "type": "FUTURE_PAYMENT",
  "product": "SEPA_CREDIT_TRANSFER",
  "requestedExecutionDate": "2026-05-01",
  "creditorAccount": { "uuid": "{creditorAccountUuid}" }
}

La fecha debe ser al menos 1 día hábil en el futuro. Solo disponible con SEPA_CREDIT_TRANSFER.

Enviar el enlace al cliente

Zertiban no envía comunicaciones al pagador. El envío del enlace y/o documento es responsabilidad de tu ERP.

Con la url y el document de la respuesta anterior tienes todo lo que necesitas:

  • Envía la url por email, WhatsApp o SMS
  • Adjunta el PDF decodificado si quieres que el cliente tenga el documento

El cliente puede acceder al cobro de dos formas:

  • Abriendo el enlace directo
  • Escaneando el QR de la página añadida al PDF (variante 1 o 3)

En cualquiera de los casos, el cliente selecciona su banco y autoriza la transferencia con su banca online (PSD2). No necesita instalar nada.

Alternativa: consulta por polling

Si prefieres no usar webhooks de momento, puedes consultar el estado directamente:

shell
# Estado de una operación
curl https://nc-api-sandbox.zertiban.com/flow/v1/operations/{operationUuid} \
  -H "Authorization: Bearer {access_token}" \
  -H "x-tenant-id: {businessUuid}"

Respuesta (campos principales):

json
{
  "uuid": "b7aa09fe-...",
  "externalId": "OP-2026-001",
  "status": "COMPLETED",
  "type": "PAYMENT",
  "flowUuid": "28ffd216-...",
  "configurationUuid": "...",
  "expirationOffset": "P30D",
  "expiresAt": "2026-05-01T09:15:00Z",
  "createdAt": "2026-04-01T09:15:00Z",
  "payment": {
    "paymentUuid": "...",
    "amount": 15075,
    "currency": "EUR",
    "concept": "Factura 2026-001",
    "types": ["PSD2_PAYMENT"],
    "psd2Payment": {
      "type": "SINGLE_PAYMENT",
      "product": "SEPA_CREDIT_TRANSFER",
      "creditorAccount": { "uuid": "..." }
    },
    "subject": {
      "name": "Ana",
      "lastName": "Martinez",
      "email": "[email protected]"
    }
  },
  "invoice": {
    "externalId": "INV-2026-001",
    "dueDate": null,
    "collectedAmount": null
  }
}

Referencia de campos

POST/pagafactu/v1/flows/pagafactu, Content-Type: multipart/form-data

El body es un part llamado payload con type=application/json.

Raíz:

CampoTipoObligatorioDescripción
externalIdStringNoTu ID para este cobro. Aparece en webhooks y consultas. Si se repite → 409 Conflict.
countryCodeStringNoCódigo ISO 3166 alpha-2 (ej. "ES").
operationsArrayExactamente 1 elemento para PagaFactu.

operations[0]:

CampoTipoObligatorioDescripción
externalIdString (max 50)NoTu ID para esta operación. Aparece en resource.externalId de los webhooks.
configurationUUIDNoTu configurationUuid. Si se omite, se usa la configuración predeterminada activa del business. Si se informa con formato no UUID → 400. Si la configuración informada está deshabilitada → 409 (no se aplica la predeterminada como fallback).
paymentObjectDatos del pago.
invoiceObjectDatos de la factura/documento.

operations[0].payment:

CampoTipoObligatorioDescripción
amountEnteroImporte en céntimos. 15075 = 150,75 EUR. Rango: 1–9.999.999.
currencyStringISO 4217. Siempre "EUR".
typesArraySiempre ["PSD2_PAYMENT"].
conceptString (max 140)Concepto visible en la app bancaria del pagador al autorizar.
psd2PaymentObjectConfiguración del pago PSD2.
debtorObjectNoDatos del pagador.

operations[0].payment.psd2Payment:

CampoTipoObligatorioDescripción
typeStringNo"SINGLE_PAYMENT" (inmediato) o "FUTURE_PAYMENT" (fecha programada).
productStringNo"SEPA_CREDIT_TRANSFER" (1–2 días) o "INSTANT_SEPA_CREDIT_TRANSFER" (segundos).
requestedExecutionDateString YYYY-MM-DDSolo con FUTURE_PAYMENTDebe ser al menos 1 día hábil en el futuro.
creditorAccount.uuidUUIDNoTu creditorAccountUuid. Si omites el bloque creditorAccount, se usa la cuenta beneficiaria predeterminada activa del business. Si lo informas con formato inválido (objeto vacío o uuid no UUID) → 400. La cuenta resuelta (explícita o automática) debe estar ACTIVE o se rechaza con 409. En la respuesta 201 Zertiban devuelve la cuenta finalmente resuelta en operations[].payment.psd2Payment.creditorAccount.uuid.

operations[0].payment.debtor:

CampoTipoObligatorioDescripción
nameString (max 100)Sí, si incluyes debtorNombre del pagador.
lastNameString (max 100)Sí, si incluyes debtorApellido del pagador.
emailString (max 255)NoFormato email válido.
phoneString (max 20)NoFormato E.164 (ej. "+34600123456").

operations[0].invoice:

CampoTipoObligatorioDescripción
externalIdStringEl ID de la factura en tu sistema.
generateDocumentBooleantrue: genera PDF con QR (o añade página PagaFactu a tu PDF). false: no genera ni devuelve documento.
nameStringSí con generateDocument: trueNombre del documento.
idStringSolo si adjuntas PDFDebe coincidir exactamente con el nombre del part multipart del fichero.
dueDateString YYYY-MM-DDNoFecha de vencimiento de la factura.
collectedAmountObjectNoImporte ya cobrado previamente.

operations[0].invoice.collectedAmount:

CampoTipoObligatorioDescripción
amountEnteroSí, si incluyes collectedAmountEn céntimos.
currencyStringSí, si incluyes collectedAmountISO 4217 (ej. "EUR").

Respuesta de creación (201 Created)

CampoTipoDescripción
uuidUUIDIdentificador del flujo en Zertiban.
createdAtISO 8601Fecha de creación.
operations[0].uuidUUIDIdentificador de la operación.
operations[0].urlURIEnlace de pago para el cliente.
operations[0].documentString Base64PDF generado. Solo presente si generateDocument: true.

Endpoints comunes

Los endpoints satélite (consultar operación, listar, cancelar, historial, estadísticas, etc.) son comunes con ZertiPay y están documentados en Endpoints de negocio.

Errores frecuentes

CódigoCausaSolución
400Payload inválido (campo faltante, formato incorrecto, más de 1 operación, configuration o creditorAccount.uuid con formato no UUID)Revisa los campos obligatorios en la sección de referencia
401Token expirado o inválidoSolicita un nuevo token
403x-tenant-id incorrecto o sin permisosVerifica que el businessUuid es correcto
404UUID de configuración o cuenta no encontradoVerifícalos en el Dashboard
409externalId duplicadoUsa un valor único por petición
409 FLOW-SERVICE-CREDITOR-ACCOUNT-NOT-ACTIVELa cuenta beneficiaria resuelta (explícita o por defecto) no está ACTIVEActiva la cuenta en el Dashboard o usa otra ACTIVE
409 FLOW-SERVICE-DEFAULT-CREDITOR-ACCOUNT-NOT-FOUNDNo has informado creditorAccount y el business no tiene cuenta beneficiaria predeterminada activaMarca una cuenta como predeterminada en el Dashboard o informa creditorAccount.uuid explícitamente
409 (configuración deshabilitada)El configuration informado existe pero está deshabilitadoHabilítalo en el Dashboard o usa otro configurationUuid
409 (sin configuración por defecto)Omitiste configuration y el business no tiene una configuración predeterminada activaMarca una configuración como predeterminada o informa configuration explícitamente

Checklist de integración PagaFactu

Configuración (una sola vez en el Dashboard)

  • Negocio creado → businessUuid
  • Configuración de flujo creada → configurationUuid
  • Credenciales API creadas → clientId + clientSecret
  • IBAN registrado por Zertiban → creditorAccountUuid
  • Webhook registrado por Zertiban → webhookSecret
  • clientSecret y webhookSecret guardados en un gestor de secretos (no en el código)

Autenticación

  • Token obtenido con Basic Auth (clientId:clientSecret en el header, no en el body)
  • Token cacheado y renovado antes de expirar (no uno por petición)
  • 401 dispara renovación + reintento una vez

Creación del cobro

  • Primer 201 obtenido en Sandbox → tengo uuid, operations[0].uuid y url
  • Si generateDocument:true → tengo el PDF en operations[0].document
  • externalId único por petición (uso mi ID de negocio, no un UUID de Zertiban)
  • amount enviado en céntimos verificado con un caso límite (p.ej. 1, 9.999.999)
  • Variante de pago programada (FUTURE_PAYMENT) probada si voy a usarla
  • Errores 4xx (400/403/404/409) se loguean con el código exacto y no se reintentan

Distribución al pagador

  • Enlace abierto en modo incógnito y desde móvil
  • Pago completado en Sandbox con un banco real (importe mínimo)

Webhooks y reconciliación

  • Endpoint público accesible desde internet (no localhost) y siempre HTTPS
  • Endpoint responde 2xx en menos de 5 s; el trabajo pesado va a una cola
  • Firma HMAC verificada antes de procesar el body
  • Deduplicación por eventUuid (idempotencia a nivel de evento)
  • Factura actualizada en el ERP tras OPERATION_COMPLETED usando mi externalId
  • Eventos fuera de orden tolerados (no degradar de COMPLETED a procesando)

Escenarios negativos probados

  • Operación cancelada por el pagador → estado y webhook correctos
  • Operación caducada → expiresAt respetado
  • Webhook entregado dos veces → no se duplica el cobro
  • 5xx de Zertiban → reintento con backoff exponencial; 4xx → sin reintento
  • Reenvío manual de un webhook reproduce el mismo resultado

Observabilidad

  • Logs incluyen externalId y operationUuid, NO el access_token ni el webhookSecret
  • Alerta si no se reciben webhooks durante X minutos en horario laboral
  • Métrica de % de cobros completados vs creados

Paso a producción

  • Todo lo anterior verde en Sandbox
  • Credenciales y URLs cambiadas a producción en el gestor de secretos
  • Contactar con Zertiban para activación en Producción

Estado final

Una integración correcta de PagaFactu:

  • No depende de UUIDs internos de Zertiban: usa tu externalId como clave de negocio para reconciliar.
  • Usa webhooks como fuente principal de verdad del estado de los cobros.
  • Evita el polling en producción: resérvalo para arranque/pruebas o como red de seguridad.