Señal manual de pago confirmado
En la web, solo el dueño puede enviar una señal real al webhook. POST /v1/paymentlink/LINK_ID/paid-signal o POST /v1/subscriptions/SUBSCRIPTION_ID/paid-signal, con Authorization: Bearer TOKEN_DEL_DUENO y cuerpo JSON vacío. Empleados, clientes y API keys reciben acceso denegado.
En suscripciones se usa el enlace más reciente existente. Se requiere un webhook configurado. Envía status paid, sin pago bancario, con metadata confirmation_source=owner_manual y confirmed_by=ID_DEL_DUENO. No modifica el estado del enlace, la suscripción ni sus fechas; no verifica movimientos ni descuenta créditos. El receptor puede ejecutar sus acciones de activación.
Devuelve delivered, payment_link_id y webhook_response_id si el receptor acepta la entrega. Los intentos quedan en el historial de webhooks y se firman con el mecanismo habitual. Ante un error revisa ese historial antes de reenviar: una respuesta perdida puede haber sido procesada por el receptor.
API de Suscripciones
Una suscripción genera un link de pago cada período y le avisa al cliente por el canal que elijas.
Endpoints
Todos requieren autenticación (Authorization: Bearer o el header appKey).
| Método | Ruta | Para qué |
|---|---|---|
| POST | /v1/subscription/make | Crear una suscripción |
| POST | /me/subscriptions | Listar paginado, con filtros |
| GET | /subscriptions/:id/details | Detalle con sus renovaciones y links |
| POST | /v1/subscriptions/:id/cancelbyowner | Cancelar |
Crear una Suscripción
curl -X POST https://api.pabilo.app/v1/subscription/make \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TU_API_KEY" \
-d '{
"name": "Plan Premium",
"description": "Acceso mensual",
"currency": "USD",
"notificationMedium": "email",
"user_bank_id": "685725869e6febc736848bf2",
"pay_first": true,
"webhook_url": "https://tu-sitio.com/webhook/pabilo",
"items": [
{ "product_id": "ID_DEL_PRODUCTO_CATALOGO", "quantity": 1 },
{ "name": "Mantenimiento", "unit_price": 5, "quantity": 1 }
],
"uniqueClient": {
"clientName": "Juan Pérez",
"clientPhone": "+584121234567",
"clientEmail": "[email protected]"
}
}'Body de la Petición
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| name | string | ✅ Sí | Nombre de la suscripción |
| description | string | ✅ Sí | Se muestra al cliente en cada aviso |
| user_bank_id | string | ✅ Sí | Cuenta bancaria donde se cobra. Cómo obtener el ID |
| currency | string | ❌ Opcional | VEF (default), USD, EUR, USDT |
| notificationMedium | string | ❌ Opcional | email (default), whatsapp, sms. Ver abajo |
| pay_first | boolean | ❌ Opcional | true: nace pending y espera el primer pago. false: nace active |
| webhook_url | string | ❌ Opcional | Se propaga a cada link de pago que genere la suscripción |
| items | array | ⚠️ Uno de los tres | Desglose de lo que se cobra. Si viene, gana sobre uniqueProduct y branchProductId |
| uniqueProduct | object | ⚠️ Uno de los tres | { productName, productPrice } |
| branchProductId | string | ⚠️ Uno de los tres | ID de un producto del catálogo. Valida pertenencia: da 404 si es de otra cuenta. |
| uniqueClient | object | ⚠️ Uno de los dos clientes | { clientName, clientPhone, clientEmail } |
| branchClientId | string | ⚠️ Uno de los dos clientes | ID de un cliente registrado. Valida pertenencia: da 404 si es de otra cuenta. |
Semántica de precio (catálogo vs pactado)
- Catálogo (
product_idobranchProductId): Referencia viva. Si editas el precio del producto, las futuras renovaciones cobrarán el nuevo precio. - Ad-hoc (líneas sin product_id o
uniqueProduct): Precio pactado. Queda congelado y siempre cobra lo mismo.
Nota: Una vez que se genera una factura (link de pago), sus precios están congelados al momento de emitirse. Editar el catálogo no afecta a facturas pendientes.
Respuesta de Ejemplo
{
"message": "Subscription created successfully",
"subscription": {
"id": "6a662432d95326f46d526271",
"created_at": "2026-08-01T12:00:00Z",
"updated_at": "2026-08-01T12:00:00Z",
"name": "Plan Premium",
"description": "Acceso mensual",
"subscriptionPeriodType": "month",
"renovationDate": null,
"renovationIsPaid": false,
"status": "pending",
"renovationCount": 0,
"renovationLimitType": "",
"clientType": "unique",
"uniqueClient": {
"clientName": "Juan Pérez",
"clientPhone": "+584121234567",
"clientEmail": "[email protected]",
"clientCode": ""
},
"items": [
{
"product_id": "ID_DEL_PRODUCTO_CATALOGO",
"name": "Plan Premium",
"quantity": 1,
"unit_price": 25,
"currency": "USD"
}
],
"statusAt": "2026-08-01T12:00:00Z",
"payFirst": true,
"userBankId": "685725869e6febc736848bf2",
"userId": "685725869e6febc736848bf1",
"webhook": "https://tu-sitio.com/webhook/pabilo",
"currency": "USD",
"notificationMedium": "email"
}
}Nota el detalle: al crear se envía webhook_url, pero en la respuesta el campo se llama webhook.
Medio de Notificación
Cada suscripción declara un solo canal por el que se le avisa al cliente del cobro, la renovación y el vencimiento. Los medios son excluyentes: con email no sale WhatsApp, y viceversa.
| Valor | Dato de contacto que exige | Disponibilidad |
|---|---|---|
| Correo del cliente | Siempre disponible. Es el valor por defecto | |
| Teléfono del cliente | Requiere habilitación | |
| sms | Teléfono del cliente | Requiere habilitación |
WhatsApp y SMS se habilitan por cuenta
400 con notificationMedium sms is not enabled for this account. Escríbele a soporte para activarlos.De dónde sale el contacto
Depende de si el cliente es único o del catálogo:
- Cliente único: de
uniqueClient.clientEmailyuniqueClient.clientPhone. - Cliente de sucursal: del
emaily elphonedel cliente registrado. Si ese cliente no tiene correo, no puedes usar el medioemailhasta que se lo agregues con la API de clientes.
Hace falta al menos uno de los dos datos, y el que exija el medio elegido. Un teléfono que el parser no entiende cuenta como ausente: debe empezar por +, 0, 4 o 58.
Errores al crear
Todos llegan con status 400 y "error": "BAD_REQUEST". El message es la cadena completa, tal cual la recibes.
| message | Causa |
|---|---|
| bad request: invalid currency, supported: VEF, USD, EUR, USDT | Moneda no soportada |
| bad request: invalid notificationMedium, supported: sms, whatsapp, email | Medio fuera del enum |
| bad request: notificationMedium sms is not enabled for this account | El medio no está habilitado para tu cuenta |
| bad request: uniqueClient or branchClientId is required | No mandaste ningún cliente |
| bad request: uniqueProduct or branchProductId is required | No mandaste ningún producto |
| bad request: client phone or email is required: error building subscription | El cliente no tiene ni teléfono ni correo |
| bad request: notificationMedium email requires a client email: error building subscription | Medio email sin correo |
| bad request: notificationMedium whatsapp requires a valid client phone: error building subscription | Medio whatsapp o sms sin teléfono válido |
| bad request: product is required: error building subscription | Producto vacío tras validar |
| bad request: client is required: error building subscription | Cliente vacío tras validar |
El catálogo completo de códigos está en Errores de la API.
Ciclo de vida
El período es mensual. Cada cobro genera un link de pago que hereda el webhook_url de la suscripción, así que tu sistema se entera igual que con un link suelto.
| Estado | Qué significa |
|---|---|
| pending | Esperando pago. Es el estado inicial con pay_first: true, y al que vuelve cuando vence una renovación impaga |
| active | Al día |
| cancelled | Cancelada por el dueño o automáticamente por falta de pago |
| inactive | Pausada |
| completed | Llegó a su límite de renovaciones |
Avisos automáticos
Un proceso diario recorre las suscripciones y notifica al cliente por su notificationMedium:
- 4 días y 1 día antes de la fecha de renovación: recordatorio con el link de pago.
- El día de la renovación, si no está pagada: la suscripción pasa a
pendingy se avisa que el pago está pendiente. - 3 días después sin pagar: se cancela automáticamente, se avisa al cliente y se anulan los links de pago pendientes.
El webhook es independiente del medio
notificationMedium decide cómo se le avisa al cliente. El webhook siempre le avisa a tu sistema, sin importar el medio.Listar Mis Suscripciones
curl -X POST "https://api.pabilo.app/me/subscriptions?page=1&limit=10&status=active" \
-H "Authorization: Bearer TU_API_KEY"El método declarado es QUERY, pero el servidor lo registra como POST, así que ambos funcionan.
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
| page | integer | Número de página (default: 1) |
| limit | integer | Resultados por página (default: 10) |
| status | string | Filtrar por estado |
| search | string | Buscar por ID de suscripción |
{
"message": "Subscriptions fetched successfully",
"subscriptions": [ … ],
"total": 1,
"page": 1,
"limit": 10
}Detalle de una suscripción
Devuelve la suscripción con el cliente y el producto ya resueltos, más el historial de renovaciones con su link de pago.
curl -X GET https://api.pabilo.app/subscriptions/6a662432d95326f46d526271/details \
-H "Authorization: Bearer TU_API_KEY"{
"message": "Subscriptions fetched successfully",
"data": {
"subscription": { … },
"subscriptionRenovationsPaymentLinks": [ … ]
}
}Cancelar una Suscripción
Pasa la suscripción a cancelled, anula sus links de pago no cobrados y le avisa al cliente por su medio de notificación.
curl -X POST https://api.pabilo.app/v1/subscriptions/6a662432d95326f46d526271/cancelbyowner \
-H "Authorization: Bearer TU_API_KEY"{
"message": "Subscriptions fetched successfully",
"data": { "subscription": { "status": "cancelled", … } }
}El message de esta respuesta dice «fetched» por un copy-paste histórico; la cancelación sí se aplicó. Guíate por subscription.status. Cancelar una ya cancelada devuelve 400 con bad request: subscription is already cancelled.
No hay endpoint para editar una suscripción
Cobros, clientes y productos
Usa el mismo client_id para identificar al comprador, tenga o no credenciales B2B2B. Asócialo en Betaserio, enlaces y suscripciones para consultar lo cobrado por cliente y descargar reportes.
Ver parámetros, ejemplos y API de reportes JSON / CSV