Listar Links de Pago
Obtén una lista paginada de los links de pago de tu cuenta (o de tu proyecto, si usas una API key de proyecto), con filtros por estado, modo de prueba, tipo de link y nombre del cliente.
Endpoint
GET /v1/payment-linksAutenticación
Requiere el header X-API-Key. Consulta la .
Los links que ves dependen de la API key ():
- API key de la cuenta: lista todos los links de pago de la cuenta.
- API key de proyecto: lista solo los links del proyecto, es decir, los creados con la API key de ese proyecto ( y los ). Los links creados desde el dashboard o con la API key de otro proyecto no aparecen en la lista ni cuentan en
total.
Parámetros de consulta
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
lim | integer | 10 | Cantidad de resultados por página, de 1 a 100 (un valor mayor devuelve 400) |
off | integer | 0 | Número de resultados a saltar (entero no negativo) |
status | string | — | Filtrar por estado guardado: pending, processing, completed, cancelled, expired. Ver |
isTest | boolean | — | Filtrar por modo de prueba. true devuelve los links de prueba; cualquier otro valor (por ejemplo false) devuelve los de producción |
linkType | string | — | Filtrar por tipo de link: one_time, customer_collection, customer_payment |
search | string | — | Solo links cuyo nombre de cliente contiene este texto (1 a 100 caracteres) |
paymentProvider | string | — | Se acepta pero se ignora en las solicitudes con API key: la lista y total no se filtran por proveedor. Un valor fuera de nmi, emetec, yappy o paypal devuelve 400 |
Los resultados se ordenan del más reciente al más antiguo (createdAt descendente). Los parámetros desconocidos se ignoran. La respuesta no trae un objeto de paginación: para recorrer todas las páginas, aumenta off en el tamaño de página hasta reunir total resultados.
processing significa que un pago del link se está confirmando. Con PayPal puede durar desde minutos hasta varios días mientras PayPal revisa el pago; después el link pasa a completed, o vuelve a pending si el pago se rechaza o se abandona.
Links vencidos
status=expired solo devuelve los links que ya están guardados como vencidos. Un link pending cuyo expiresAt ya pasó se guarda como expired la primera vez que un listado (la API o el dashboard) lo devuelve en una página; esa misma respuesta todavía lo muestra como pending, y las lecturas posteriores lo muestran como expired.
Por eso, paginar con status=pending y off puede saltarse links: cada página saca de ese filtro los links vencidos que acaba de devolver, y el siguiente off cae más adelante de lo que esperas. Para encontrar todos los links vencidos, recorre las páginas sin filtro de status y quédate con los links cuyo status es expired, o pending con un expiresAt en el pasado (como en ). No se envía ningún webhook cuando un link vence.
Ejemplos de solicitud
Solicitud básica
curl -X GET "https://api-pay.zelta.dev/v1/payment-links" \
-H "X-API-Key: tu-api-key-aqui" \
-H "Content-Type: application/json"Con paginación
curl -X GET "https://api-pay.zelta.dev/v1/payment-links?lim=20&off=40" \
-H "X-API-Key: tu-api-key-aqui" \
-H "Content-Type: application/json"Con filtros
curl -X GET "https://api-pay.zelta.dev/v1/payment-links?status=completed&isTest=false&lim=10" \
-H "X-API-Key: tu-api-key-aqui" \
-H "Content-Type: application/json"Formato de respuesta
{
"success": true,
"data": {
"paymentLinks": [
{
"id": "7312345678901234567",
"paymentLinkUrl": "https://pay.zelta.dev/Xk3v9Q_aB7mN2pL8rT4wYz1c",
"customerName": "Maria Garcia",
"concept": "Pago de servicio mensual",
"amount": 5000,
"createdAt": "2026-03-13T10:30:00.000Z",
"expiresAt": "2026-03-16T10:30:00.000Z",
"status": "pending",
"isTest": false,
"redirectUrl": "https://mitienda.com/confirmacion",
"metadata": {
"orderId": "ORD-2026-0042"
},
"paymentMethods": ["card", "yappy"],
"requestCustomerEmail": true
}
],
"total": 1
},
"message": "Operation completed successfully",
"timestamp": "2026-03-13T10:30:00.000Z"
}Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
paymentLinks | array | Lista de links de pago de la página |
total | integer | Total de links que coinciden con los filtros, entre los que alcanza la API key |
paymentLinks[].id | string | Identificador único del link (cadena numérica) |
paymentLinks[].paymentLinkUrl | string | URL de la página de pago. Su último segmento es el hashUrl que usan y |
paymentLinks[].customerName | string | Nombre del cliente |
paymentLinks[].concept | string | Concepto o descripción del cobro |
paymentLinks[].amount | integer | Monto en centavos |
paymentLinks[].createdAt | string | Fecha de creación (ISO 8601) |
paymentLinks[].expiresAt | string | Fecha de vencimiento (ISO 8601) |
paymentLinks[].status | string | Estado guardado: pending, processing, completed, cancelled, expired. Un link vencido puede seguir apareciendo como pending (ver ) |
paymentLinks[].isTest | boolean | Si es un link de prueba |
paymentLinks[].redirectUrl | string | URL de redirección después del pago. Se omite si no existe |
paymentLinks[].metadata | object | Datos personalizados asociados al link. Se omite si no existe |
paymentLinks[].paymentMethods | array | Métodos de pago permitidos ("card", "yappy", "paypal"). Se omite si no se especificaron |
paymentLinks[].requestCustomerEmail | boolean | Si la página de pago pide el email al cliente |
paymentLinks[].completedAt | string | Fecha en que se completó el pago. Se omite mientras no exista |
paymentLinks[].cancelledAt | string | Fecha de cancelación. Se omite mientras no exista |
La lista no incluye customerEmail; usa para leerlo. Los campos opcionales se omiten cuando no tienen valor; nunca llegan como null.
Códigos de estado
| Código | Descripción |
|---|---|
200 | Lista obtenida exitosamente |
400 | Parámetros de consulta inválidos |
401 | Falta el header X-API-Key |
403 | Cuenta suspendida o proyecto inactivo |
404 | API key desconocida (ERR_ACCOUNT_API_KEY_NOT_FOUND) |
429 | Rate limit excedido (cuerpo en texto plano; espera Retry-After segundos) |
Respuestas de error
Parámetros inválidos
Los parámetros inválidos devuelven 400 con el resultado de la validación: error.issues lista cada parámetro que falló. Ejemplo con lim=500:
{
"success": false,
"error": {
"issues": [
{
"code": "too_big",
"maximum": 100,
"type": "number",
"inclusive": true,
"exact": false,
"message": "Number must be less than or equal to 100",
"path": ["lim"]
}
],
"name": "ZodError"
}
}API key faltante
{
"success": false,
"message": "Missing X-API-Key header",
"error": {
"code": "ERR_API_KEY_MISSING"
},
"timestamp": "2026-03-13T10:30:00.000Z"
}API key inválida
Una llave desconocida, regenerada o eliminada devuelve 404:
{
"success": false,
"message": "Api key not found",
"error": {
"code": "ERR_ACCOUNT_API_KEY_NOT_FOUND"
},
"timestamp": "2026-03-13T10:30:00.000Z"
}Ejemplos de implementación
Obtener todos los links con paginación
async function getAllPaymentLinks() {
const allLinks = [];
const limit = 100; // tamaño máximo de página
let offset = 0;
while (true) {
const response = await fetch(
`https://api-pay.zelta.dev/v1/payment-links?lim=${limit}&off=${offset}`,
{
headers: {
'X-API-Key': process.env.ZELTA_API_KEY,
'Content-Type': 'application/json'
}
}
);
if (response.status === 429) {
const retryAfter = Number(response.headers.get('Retry-After') ?? 60);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message ?? JSON.stringify(result.error?.issues));
}
allLinks.push(...result.data.paymentLinks);
if (result.data.paymentLinks.length === 0 || allLinks.length >= result.data.total) {
break;
}
offset += limit;
}
return allLinks;
}Filtrar links por estado
async function getPaymentLinksByStatus(status) {
const response = await fetch(
`https://api-pay.zelta.dev/v1/payment-links?status=${status}`,
{
headers: {
'X-API-Key': process.env.ZELTA_API_KEY,
'Content-Type': 'application/json'
}
}
);
const result = await response.json();
if (!result.success) {
throw new Error(result.message ?? JSON.stringify(result.error?.issues));
}
return result.data;
}
// Obtener solo links completados
const completed = await getPaymentLinksByStatus('completed');Casos de uso
Vista general de tus links
Usa la paginación para mostrar links de pago en una tabla con navegación por páginas:
async function getLinksPage(page, perPage = 20) {
const offset = (page - 1) * perPage;
const response = await fetch(
`https://api-pay.zelta.dev/v1/payment-links?lim=${perPage}&off=${offset}`,
{
headers: {
'X-API-Key': process.env.ZELTA_API_KEY,
'Content-Type': 'application/json'
}
}
);
const result = await response.json();
const totalPages = Math.ceil(result.data.total / perPage);
return {
links: result.data.paymentLinks,
total: result.data.total,
currentPage: page,
totalPages
};
}Gestión de órdenes pendientes
Para hacer seguimiento a pagos no completados, recorre tus links sin filtro de estado y separa los que todavía se pueden pagar de los vencidos:
async function getOpenOrders() {
const links = await getAllPaymentLinks(); // ver "Obtener todos los links con paginación"
const now = new Date();
const payable = links.filter(l => l.status === 'pending' && new Date(l.expiresAt) > now);
const expired = links.filter(l =>
l.status === 'expired' || (l.status === 'pending' && new Date(l.expiresAt) <= now)
);
return { payable, expired };
}Análisis de pagos
Obtén estadísticas de tus links teniendo en cuenta que un link vencido puede seguir apareciendo como pending:
async function getPaymentStats() {
const links = (await getAllPaymentLinks()).filter(l => !l.isTest);
const now = new Date();
const completed = links.filter(l => l.status === 'completed');
const totalAmount = completed.reduce((sum, link) => sum + link.amount, 0);
return {
completed: completed.length,
pending: links.filter(l => l.status === 'pending' && new Date(l.expiresAt) > now).length,
processing: links.filter(l => l.status === 'processing').length,
cancelled: links.filter(l => l.status === 'cancelled').length,
expired: links.filter(l =>
l.status === 'expired' || (l.status === 'pending' && new Date(l.expiresAt) <= now)
).length,
totalAmountCents: totalAmount,
totalAmountDollars: (totalAmount / 100).toFixed(2)
};
}Buenas prácticas
- Usa paginación: usa
lim(máx. 100) yoffpara recorrer tus links por páginas. - Filtra en el servidor: usa
status,isTest,linkTypeysearchen lugar de filtrar todo en tu aplicación. Para links vencidos, sigue la recomendación de . - Respeta el rate limit: si recorres muchas páginas, espacia las solicitudes para no exceder las 60 por ventana de 60 segundos.
- Maneja errores: verifica el código de estado y el campo
successantes de procesar los datos.
Siguientes pasos
- — Crea un nuevo link de pago
- — Consulta un link específico
- — Cancela un link pendiente
- — Recibe notificaciones de pago en tiempo real