Developer Docs
Español
Español
Diagrama, llamadas paso a paso y ejemplos en múltiples lenguajes.
Los pasos de la WebApp y del banco ocurren sin tu intervención. Tu ERP solo interactúa con la API Cliente (flechas con ZBN).
El endpoint usa multipart/form-data para permitir adjuntar documentos PDF en la misma petición. La estructura es:
POST /flow/v1/flows
Content-Type: multipart/form-data; boundary=----boundary
------boundary
Content-Disposition: form-data; name="payload"
Content-Type: application/json ← obligatorio
{ "externalId": "...", "operations": [...] }
------boundary
Content-Disposition: form-data; name="doc-factura"; filename="factura.pdf"
Content-Type: application/pdf
<binario PDF>
------boundary--**El part **payload debe llevar Content-Type: application/json. Si no, el servidor devuelve 400.
curl -X POST https://api-sandbox.zertiban.com/flow/v1/flows \
-H "Authorization: Bearer {access_token}" \
-H "x-tenant-id: {businessUuid}" \
-F 'payload={
"externalId": "COBRO-2026-001",
"countryCode": "ES",
"operations": [{
"id": "op-1",
"type": "PAYMENT",
"externalId": "OP-2026-001",
"configuration": "{configurationUuid}",
"payment": {
"amount": 15075,
"currency": "EUR",
"concept": "Cobro 2026-001",
"types": ["PSD2_PAYMENT"],
"psd2Payment": {
"type": "SINGLE_PAYMENT",
"product": "SEPA_CREDIT_TRANSFER",
"creditorAccount": { "uuid": "{creditorAccountUuid}" }
},
"debtor": { "name": "Ana", "lastName": "Martinez", "email": "[email protected]" }
}
}]
};type=application/json'import requests, json
payload = {
"externalId": "COBRO-2026-001", "countryCode": "ES",
"operations": [{ "id": "op-1", "type": "PAYMENT", "externalId": "OP-2026-001",
"configuration": CONFIGURATION_UUID,
"payment": { "amount": 15075, "currency": "EUR", "concept": "Cobro 2026-001",
"types": ["PSD2_PAYMENT"],
"psd2Payment": { "type": "SINGLE_PAYMENT", "product": "SEPA_CREDIT_TRANSFER",
"creditorAccount": {"uuid": CREDITOR_ACCOUNT_UUID} } } }]
}
response = requests.post(
"https://api-sandbox.zertiban.com/flow/v1/flows",
headers={"Authorization": f"Bearer {access_token}", "x-tenant-id": BUSINESS_UUID},
files={"payload": (None, json.dumps(payload), "application/json")}
)
data = response.json()
payment_url = data["operations"][0]["url"]const FormData = require('form-data');
const form = new FormData();
form.append('payload', JSON.stringify(payload), { contentType: 'application/json' });
const response = await axios.post('https://api-sandbox.zertiban.com/flow/v1/flows', form, {
headers: { Authorization: `Bearer ${accessToken}`, 'x-tenant-id': BUSINESS_UUID, ...form.getHeaders() }
});
const paymentUrl = response.data.operations[0].url;MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("payload", objectMapper.writeValueAsString(request)).contentType(MediaType.APPLICATION_JSON);
FlowCreatedResponse result = webClient.post().uri("/flow/v1/flows")
.header("Authorization", "Bearer " + accessToken).header("x-tenant-id", businessUuid)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(builder.build()))
.retrieve().bodyToMono(FlowCreatedResponse.class).block();Respuesta (201 Created):
{
"uuid": "28ffd216-5ee8-4e99-b1a9-511961e9c655",
"externalId": "COBRO-2026-001",
"operations": [
{
"uuid": "d1faff9e-...",
"externalId": "OP-2026-001",
"id": "op-1",
"url": "https://zertiban.com/{businessUuid}/{operationUuid}"
}
]
}
amounten céntimos:15075= 150,75 EUR. |externalIdaparece en todos los webhooks para reconciliar sin guardar UUIDs internos. Restricción actual: cada flujo contiene una sola operación de pago. Nota sobre el OpenAPI: el ficheroopenapi.yamldel servicio muestraSINGLE_PAYMENTSyFUTURE_PAYMENTS(en plural) en algunos esquemas. Es una errata. Los valores correctos, definidos en el enumPaymentTypedel código, sonSINGLE_PAYMENTyFUTURE_PAYMENT(singular).
SEPA Instant:
"psd2Payment": { "type": "SINGLE_PAYMENT", "product": "INSTANT_SEPA_CREDIT_TRANSFER",
"creditorAccount": { "uuid": "{creditorAccountUuid}" } }Pago programado (1–89 días en el futuro):
"psd2Payment": { "type": "FUTURE_PAYMENT", "product": "SEPA_CREDIT_TRANSFER",
"requestedExecutionDate": "2026-05-15", "creditorAccount": { "uuid": "{creditorAccountUuid}" } }Con PDF adjunto:
-F 'payload={ "documents":[{"id":"factura","name":"factura.pdf"}],
"operations":[{ "payment":{ "documents":[{"documentId":"factura"}] } }]
};type=application/json' \
-F 'factura=@./factura.pdf;type=application/pdf'El nombre del part del fichero debe coincidir exactamente con documents[].id.
Distribuye la url por cualquier canal. El pagador abre el enlace, selecciona su banco y autoriza la transferencia. Si configuraste redirection.callback.url, su navegador será redirigido automáticamente cuando la operación alcance un estado final.
Si prefieres no usar webhooks de momento, puedes consultar el estado directamente:
curl https://api-sandbox.zertiban.com/flow/v1/operations/{operationUuid}/status \
-H "Authorization: Bearer {access_token}" -H "x-tenant-id: {businessUuid}"POST /flow/v1/flows Content-Type: multipart/form-data.
Raíz del payload:
| Campo | Tipo | Oblig. | Descripción |
|---|---|---|---|
externalId | String | No | Tu ID del flujo |
countryCode | String | Sí | ISO 3166 (ej. "ES") |
additionalLanguage | String | No | ISO 639-1 |
operations | Array | Sí | 1 operación en Zertipay |
documents | Array | No | PDFs adjuntos (hasta 10) |
labels | Array | No | Etiquetas clave-valor |
operations[i]:
| Campo | Tipo | Oblig. | Descripción |
|---|---|---|---|
id | String | Sí | ID local del request (@NotBlank) |
type | String | Sí | "PAYMENT" |
externalId | String | No | Tu ID de la operación |
configuration | UUID | Sí | Tu configurationUuid |
payment | Object | Sí | Datos del pago |
payment:
| Campo | Tipo | Oblig. | Descripción |
|---|---|---|---|
amount | Long | Sí | Positivo, en céntimos |
currency | String | Sí | ISO 4217 (ej. "EUR") |
concept | String | Sí | Visible en la app bancaria del pagador |
types | Array<String> | Sí | Siempre ["PSD2_PAYMENT"] |
psd2Payment | Object | Sí | Configuración PSD2 |
documents | Array | No | Referencias a documents[].id |
debtor | Object | No | Datos del pagador (nombre, apellido, email, teléfono) |
psd2Payment:
| Campo | Tipo | Oblig. | Descripción |
|---|---|---|---|
type | String | Sí | "SINGLE_PAYMENT" o "FUTURE_PAYMENT" |
product | String | Sí | "SEPA_CREDIT_TRANSFER" o "INSTANT_SEPA_CREDIT_TRANSFER" |
requestedExecutionDate | YYYY-MM-DD | Solo FUTURE_PAYMENT | 1–89 días en el futuro |
creditorAccount.uuid | UUID | Sí | Tu creditorAccountUuid |
| Campo | Descripción |
|---|---|
uuid | UUID del flujo |
externalId | Tu externalId |
operations[i].uuid | UUID de la operación |
operations[i].externalId | Tu externalId de la operación |
operations[i].id | El id local que enviaste |
operations[i].url | Enlace de pago para el pagador |
Los endpoints satélite (consulta rápida, detalle, listado, cancelación, ampliación de caducidad, descarga de documentos, historial, estadísticas y detalle del pago PSD2) son comunes con PagaFactu y están documentados en Endpoints de negocio.
| Código | Causa típica | Solución |
|---|---|---|
400 | Payload inválido: campo faltante, types incorrecto (usa PSD2_PAYMENT no SINGLE_PAYMENTS), requestedExecutionDate fuera de rango 1–89 días, amount no positivo, duration sin designador D | Revisa la validación en la sección de referencia |
401 | Token expirado o credenciales inválidas | Nuevo token con Basic Auth (no en el body) |
403 | x-tenant-id incorrecto o permisos de credencial insuficientes | Verifica businessUuid y los permisos de las credenciales |
404 | UUID de configuración, cuenta, operación, flujo o pago no encontrado | Verifica con los endpoints GET |
409 | Cancelar operación ya finalizada, o con pago en curso | Consulta el estado antes |
Configuración (una sola vez en el Dashboard)
businessUuidconfigurationUuidredirection.return.url y redirection.callback.url configuradas si aplicaclientId + clientSecretcreditorAccountUuidwebhookSecretclientSecret y webhookSecret guardados en un gestor de secretos (no en el código)Autenticación
clientId:clientSecret en el header, no en el body)Creación del cobro
uuid, operations[0].uuid y urlexternalId ú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)FUTURE_PAYMENT) probada si voy a usarlaDistribución al pagador
callback.url recibe la redirección al estado final (si la configuraste)Webhooks y reconciliación
eventUuid (idempotencia a nivel de evento)OPERATION_COMPLETED usando mi externalIdCOMPLETED a procesando)Escenarios negativos probados
expiresAt respetadoObservabilidad
externalId y operationUuid, NO el access_token ni el webhookSecretPaso a producción
Una integración correcta de ZertiPay:
externalId como clave de negocio para reconciliar.