Skip to content

Referencia API ​

Documentación completa de la API REST de Zelta Pay. Esta referencia cubre todos los endpoints, formatos de datos, códigos de error y convenciones de la API.

URL Base ​

https://api-pay.zelta.dev

Usa siempre HTTPS. No existe una URL separada para pruebas: los links de prueba se crean en la misma URL con isTest: true (ver ).

Versionamiento ​

La API usa versionamiento por ruta. La versión actual es v1:

https://api-pay.zelta.dev/v1/

Todas las rutas de endpoints incluyen el prefijo /v1/. Los cambios que rompan compatibilidad se publicarán bajo una nueva versión de ruta.

Autenticación ​

Todas las solicitudes requieren el header X-API-Key:

http
X-API-Key: tu-api-key-aqui

Consulta la para obtener tu clave, conocer los tipos de API key y ver ejemplos de implementación.

Rate Limiting ​

  • 60 solicitudes por ventana fija de 60 segundos por API key, compartidas por todos los endpoints /v1
  • Headers de respuesta: RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (segundos hasta el reinicio de la ventana)
  • Al exceder el límite: 429 Too Many Requests con cuerpo en texto plano (sin código de error) y el header Retry-After con los segundos a esperar

Consulta los detalles en .

Formato de respuesta ​

Respuesta exitosa ​

json
{
  "success": true,
  "data": { ... },
  "message": "Operation completed successfully",
  "timestamp": "2026-03-13T10:30:00.000Z"
}

message siempre está presente. Algunos endpoints usan un mensaje propio (por ejemplo, "Payment link created successfully" al crear un link).

Respuesta de error ​

Los errores de negocio y de autenticación usan este formato:

json
{
  "success": false,
  "message": "No active payment provider found",
  "error": {
    "code": "ERR_NO_ACTIVE_PAYMENT_PROVIDER"
  },
  "timestamp": "2026-03-13T10:30:00.000Z"
}

Usa error.code para decidir qué hacer; el texto de message puede cambiar. error.details aparece en algunos errores con información adicional.

Errores de validación ​

Cuando el cuerpo o los parámetros de la solicitud no pasan la validación, la API responde 400 con un formato distinto: error.issues lista cada campo que falló, y no hay message, error.code ni timestamp:

json
{
  "success": false,
  "error": {
    "issues": [
      {
        "code": "too_small",
        "minimum": 0,
        "type": "number",
        "inclusive": false,
        "exact": false,
        "message": "Amount must be a positive integer",
        "path": ["amount"]
      }
    ],
    "name": "ZodError"
  }
}

Cada elemento de issues trae el campo afectado en path (un arreglo), un message legible y un code que indica el tipo de fallo (invalid_type, too_small, too_big, invalid_string, invalid_enum_value, custom, etc.). Un campo desconocido en el cuerpo produce un issue unrecognized_keys con path: [] y los nombres de los campos en keys.

Respuesta de rate limit ​

Un 429 tiene cuerpo en texto plano (Too many requests, please try again later.), no JSON. Revisa el código de estado antes de interpretar el cuerpo como JSON.

Códigos de estado HTTP ​

CódigoDescripciónUso
200OKSolicitud exitosa (GET, PATCH)
201CreatedRecurso creado exitosamente (POST)
400Bad RequestDatos inválidos (validación) o error de negocio (ej. sin proveedor activo, estado inválido)
401UnauthorizedFalta el header X-API-Key
403ForbiddenCuenta suspendida, proyecto inactivo o API key sin el alcance requerido
404Not FoundAPI key desconocida o recurso no encontrado
409ConflictConflicto de concurrencia (ej. un pago del link se está confirmando, o un cargo con la misma clave de idempotencia aún se está liquidando)
429Too Many RequestsRate limit excedido
500Internal Server ErrorError interno del servidor

Códigos de error ​

Autenticación ​

CódigoHTTP StatusDescripción
ERR_API_KEY_MISSING401No se incluyó el header X-API-Key
ERR_ACCOUNT_API_KEY_NOT_FOUND404La API key no existe (mal copiada, regenerada o eliminada)
ERR_ACCOUNT_NOT_FOUND404La cuenta asociada a la API key no existe
ERR_ACCOUNT_SUSPENDED403La cuenta está suspendida
ERR_PROJECT_NOT_FOUND404El proyecto de la API key ya no existe
ERR_PROJECT_INACTIVE403El proyecto de la API key no está activo
ERR_API_KEY_NOT_PROJECT_SCOPED403El endpoint exige una API key de proyecto

El rate limit (429) no tiene código de error: su cuerpo es texto plano.

CódigoHTTP StatusDescripción
ERR_NO_ACTIVE_PAYMENT_PROVIDER400No hay un proveedor de pago activo para el entorno del link (isTest: true usa tus proveedores de sandbox, isTest: false los de producción)
ERR_INACTIVE_PAYMENT_METHOD400Algún método de paymentMethods no está activo para el entorno del link
ERR_AMOUNT_BELOW_NMI_MINIMUM400amount entre 1 y 99 centavos: el mínimo de un link es 100 centavos (1.00 USD)
ERR_PAYMENT_LINK_NOT_FOUND404El link no existe o la API key no lo alcanza: es de otra cuenta o, con una API key de proyecto, no es de ese proyecto (ver )
ERR_INVALID_STATE400El link no puede cancelarse porque ya está completed o cancelled
ERR_PAYMENT_IN_PROGRESS409El link no puede cancelarse porque hay un pago en confirmación (processing)

Los errores de validación del cuerpo (monto fuera de rango, campos faltantes, metadata inválida, expiresInDays fuera de rango, etc.) no tienen error.code: se devuelven con el formato de .

Parámetros fields y expand ​

CódigoHTTP StatusDescripción
ERR_INVALID_FIELDS400fields incluye un campo que el endpoint no devuelve. En y , solo se aceptan los campos de su objeto
ERR_INVALID_RELATIONS400expand nombra una relación que el endpoint no expande. Los links de pago y el listado de clientes no aceptan expand; los cargos aceptan customer y product

Servidor ​

CódigoHTTP StatusDescripción
ERR_INTERNAL_SERVER_ERROR500Error inesperado. El message es "Internal Server Error"

Los códigos de error de la API de Integración se documentan en la página de cada endpoint (, , , y ).

Endpoints ​

MétodoRutaDescripciónDocumentación
GET/v1/payment-linksListar links de pago
POST/v1/payment-linksCrear un link de pago
GET/v1/payment-links/{hashUrl}Obtener un link de pago
PATCH/v1/payment-links/{hashUrl}/cancelCancelar un link de pago

{hashUrl} es el último segmento de paymentLinkUrl (24 caracteres). El id del link no sirve en estas rutas.

API de Integración (proyectos) ​

Estos endpoints requieren una API key ligada a un proyecto. Consulta la .

MétodoRutaDescripciónDocumentación
GET/v1/products, /v1/products/{productCode}Consultar el catálogo de productos
GET POST/v1/customers, /v1/customers/{id}, /v1/customers/{id}/vaultGestionar clientes y su método de pago
POST/v1/collections/links, /v1/collections/payment-linksLinks de colección y de pago-cliente
POST GET PATCH/v1/subscriptions, /v1/subscriptions/{id}/...Crear y administrar suscripciones
POST GET/v1/charges, /v1/charges/{id}Cobros únicos a clientes vaulteados

Paginación ​

Los endpoints que devuelven listas soportan paginación con los parámetros:

ParámetroTipoDefaultDescripción
liminteger10Cantidad de resultados por página, de 1 a 100 (un valor mayor devuelve 400)
offinteger0Número de resultados a saltar

Ejemplo ​

http
GET /v1/payment-links?lim=20&off=40

La respuesta incluye el campo total (resultados que coinciden con los filtros) para calcular la paginación. No hay un objeto de paginación aparte:

json
{
  "success": true,
  "data": {
    "paymentLinks": [...],
    "total": 150
  }
}

Tipos de datos ​

Identificadores ​

Los IDs (id, paymentLinkId, accountId, projectId, etc.) son cadenas numéricas sin prefijo, por ejemplo "7312345678901234567". El eventId de los webhooks es un UUID.

Montos ​

Todos los montos se expresan en centavos (integer). Los links de pago se cobran siempre en USD:

ValorEquivalente
100$1.00
1500$15.00
1000000$10,000.00

Rango permitido para un link de pago: 100 a 1000000 (es decir, $1.00 a $10,000.00).

Timestamps ​

Todas las fechas y horas usan formato ISO 8601 en UTC:

2026-03-13T10:30:00.000Z

Campos opcionales ​

Los campos opcionales de las respuestas de links de pago (customerEmail, cancelledAt, completedAt, redirectUrl, metadata, paymentMethods) se omiten cuando no tienen valor; nunca llegan como null.

EstadoDescripción
pendingLink creado, pendiente de pago
processingHay un pago en confirmación. Con PayPal puede durar desde minutos hasta varios días mientras PayPal revisa el pago. Mientras tanto el link no se puede pagar de nuevo ni cancelar
completedPago completado exitosamente
cancelledLink cancelado
expiredLink vencido sin pago, guardado como expirado

Un link vence en su expiresAt (por defecto 3 días después de crearlo por API; configurable con ). Después de expiresAt ya no se puede pagar, pero su estado guardado no cambia a expired hasta que un listado lo devuelve; por eso un link vencido puede seguir apareciendo como pending. Compara expiresAt con la hora actual para saber si todavía se puede pagar. No se envía ningún webhook cuando un link vence.

Estados de un cargo ​

Los cargos de la usan estos estados:

EstadoDescripción
pendingCargo creado, aún sin resolver
processingEn curso; el resultado final se entregará por webhook
paidPago liquidado correctamente
failedPago rechazado o fallido
unknownLa conciliación se agotó sin respuesta definitiva; requiere revisión manual
deferredCargo aprobado sin mover dinero, diferido al próximo cobro de la suscripción

Métodos de pago ​

Valores de paymentMethods al crear un link:

ValorDescripción
"card"Pago con tarjeta de crédito o débito
"yappy"Pago con Yappy
"paypal"Pago con PayPal: con cuenta de PayPal o con tarjeta sin cuenta de PayPal. Solo en links de pago únicos (POST /v1/payment-links)

Consulta para ver cómo funciona PayPal.

Metadata ​

El campo metadata permite almacenar datos personalizados en formato clave-valor:

  • Máximo 20 llaves por objeto, y no puede ser un objeto vacío
  • Llaves de 1 a 40 caracteres
  • Valores de tipo string (máx. 255 caracteres), number, boolean o null
  • Máximo 8192 bytes al serializarse como JSON
  • No almacenes datos sensibles (tarjetas, contraseñas, tokens)
json
{
  "metadata": {
    "orderId": "ORD-2026-0042",
    "source": "web-checkout",
    "userId": 4521,
    "vip": true
  }
}

Webhooks ​

Zelta Pay envía notificaciones a tu servidor cuando ocurren eventos relevantes.

Eventos disponibles ​

Zelta Pay emite eventos para pagos de links (payment.success, payment.failed), comprobantes (receipt.created) y, cuando usas la , para cargos (charge.*), suscripciones (subscription.*), facturas (invoice.*), clientes (customer.created) y links rechazados (payment_link.rejected), además del evento de prueba webhook.ping. No hay evento cuando un link vence o se cancela.

Consulta la lista completa y el payload de cada evento en la .

Seguridad ​

Cada webhook incluye el header Zeltapay-Signature con el formato t=<timestamp>, v1=<firma>. La firma v1 es un HMAC-SHA256 en hexadecimal, calculado con el secreto de tu webhook sobre el string t=<timestamp>.<cuerpo crudo>.

Consulta las guías de webhooks para más detalles:

Modo de prueba ​

Usa el campo isTest: true al crear links de pago para probar tu integración. Un link de prueba se cobra con las credenciales de sandbox que configuraste en , por lo que no mueve dinero real; un link con isTest: false usa tus credenciales de producción. Por eso necesitas un proveedor activo en el entorno correspondiente.

Puedes filtrar links de prueba con el parámetro isTest al . Los webhooks de pagos de prueba llevan transaction.isTest: true.

SDKs ​

Actualmente no hay SDKs oficiales. La API REST es compatible con cualquier lenguaje que soporte solicitudes HTTP. Consulta los para ver código en Node.js, Python, PHP, Cloudflare Workers y Hono.

Changelog ​

Consulta el para ver el historial de cambios de la API.

Soporte ​

Si tienes preguntas o necesitas ayuda con la integración:

  • Revisa la para empezar
  • Consulta los para ejemplos prácticos
  • Escribe al equipo de soporte en

Documentación oficial de Zelta