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.devUsa 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:
X-API-Key: tu-api-key-aquiConsulta 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 Requestscon cuerpo en texto plano (sin código de error) y el headerRetry-Aftercon los segundos a esperar
Consulta los detalles en .
Formato de respuesta
Respuesta exitosa
{
"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:
{
"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:
{
"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ódigo | Descripción | Uso |
|---|---|---|
200 | OK | Solicitud exitosa (GET, PATCH) |
201 | Created | Recurso creado exitosamente (POST) |
400 | Bad Request | Datos inválidos (validación) o error de negocio (ej. sin proveedor activo, estado inválido) |
401 | Unauthorized | Falta el header X-API-Key |
403 | Forbidden | Cuenta suspendida, proyecto inactivo o API key sin el alcance requerido |
404 | Not Found | API key desconocida o recurso no encontrado |
409 | Conflict | Conflicto de concurrencia (ej. un pago del link se está confirmando, o un cargo con la misma clave de idempotencia aún se está liquidando) |
429 | Too Many Requests | Rate limit excedido |
500 | Internal Server Error | Error interno del servidor |
Códigos de error
Autenticación
| Código | HTTP Status | Descripción |
|---|---|---|
ERR_API_KEY_MISSING | 401 | No se incluyó el header X-API-Key |
ERR_ACCOUNT_API_KEY_NOT_FOUND | 404 | La API key no existe (mal copiada, regenerada o eliminada) |
ERR_ACCOUNT_NOT_FOUND | 404 | La cuenta asociada a la API key no existe |
ERR_ACCOUNT_SUSPENDED | 403 | La cuenta está suspendida |
ERR_PROJECT_NOT_FOUND | 404 | El proyecto de la API key ya no existe |
ERR_PROJECT_INACTIVE | 403 | El proyecto de la API key no está activo |
ERR_API_KEY_NOT_PROJECT_SCOPED | 403 | El endpoint exige una API key de proyecto |
El rate limit (429) no tiene código de error: su cuerpo es texto plano.
Links de pago
| Código | HTTP Status | Descripción |
|---|---|---|
ERR_NO_ACTIVE_PAYMENT_PROVIDER | 400 | No 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_METHOD | 400 | Algún método de paymentMethods no está activo para el entorno del link |
ERR_AMOUNT_BELOW_NMI_MINIMUM | 400 | amount entre 1 y 99 centavos: el mínimo de un link es 100 centavos (1.00 USD) |
ERR_PAYMENT_LINK_NOT_FOUND | 404 | El 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_STATE | 400 | El link no puede cancelarse porque ya está completed o cancelled |
ERR_PAYMENT_IN_PROGRESS | 409 | El 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ódigo | HTTP Status | Descripción |
|---|---|---|
ERR_INVALID_FIELDS | 400 | fields incluye un campo que el endpoint no devuelve. En y , solo se aceptan los campos de su objeto |
ERR_INVALID_RELATIONS | 400 | expand 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ódigo | HTTP Status | Descripción |
|---|---|---|
ERR_INTERNAL_SERVER_ERROR | 500 | Error 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
Links de pago
| Método | Ruta | Descripción | Documentación |
|---|---|---|---|
GET | /v1/payment-links | Listar links de pago | |
POST | /v1/payment-links | Crear un link de pago | |
GET | /v1/payment-links/{hashUrl} | Obtener un link de pago | |
PATCH | /v1/payment-links/{hashUrl}/cancel | Cancelar 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étodo | Ruta | Descripción | Documentación |
|---|---|---|---|
GET | /v1/products, /v1/products/{productCode} | Consultar el catálogo de productos | |
GET POST | /v1/customers, /v1/customers/{id}, /v1/customers/{id}/vault | Gestionar clientes y su método de pago | |
POST | /v1/collections/links, /v1/collections/payment-links | Links 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á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 |
Ejemplo
GET /v1/payment-links?lim=20&off=40La respuesta incluye el campo total (resultados que coinciden con los filtros) para calcular la paginación. No hay un objeto de paginación aparte:
{
"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:
| Valor | Equivalente |
|---|---|
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.000ZCampos 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.
Estados de un link de pago
| Estado | Descripción |
|---|---|
pending | Link creado, pendiente de pago |
processing | Hay 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 |
completed | Pago completado exitosamente |
cancelled | Link cancelado |
expired | Link 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:
| Estado | Descripción |
|---|---|
pending | Cargo creado, aún sin resolver |
processing | En curso; el resultado final se entregará por webhook |
paid | Pago liquidado correctamente |
failed | Pago rechazado o fallido |
unknown | La conciliación se agotó sin respuesta definitiva; requiere revisión manual |
deferred | Cargo aprobado sin mover dinero, diferido al próximo cobro de la suscripción |
Métodos de pago
Valores de paymentMethods al crear un link:
| Valor | Descripció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,booleanonull - Máximo 8192 bytes al serializarse como JSON
- No almacenes datos sensibles (tarjetas, contraseñas, tokens)
{
"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