Guía para desarrolladores
Sitio de VEXPayReferencia de API
Explorar documentación

Suscripciones y cobros recurrentes

Cobra a tus clientes cada día, semana, mes o año. Los precios van en dólares y cada factura se paga en bolívares a la tasa del día, por enlace o con débito automático. VEX Pay emite las facturas, les escribe a tus clientes, reintenta el cobro y cancela por ti.

Qué obtienes

Con las suscripciones conviertes una venta única en ingresos recurrentes sin construir un sistema de facturación. Defines el plan una sola vez; VEX Pay genera una factura en cada período, se la envía a tu cliente a tu nombre, la cobra, hace seguimiento cuando no se paga y le avisa a tu servidor de cada paso con webhooks.

  • Precios en dólares, pagos en bolívares. Un plan cuesta 10.00 USD y el cliente paga el equivalente en Bs a la tasa vigente de tu cuenta (BCV o mercado, según la que uses) en el momento en que paga, así tus ingresos no pierden valor de un período a otro.
  • Dos formas de cobrar. send_invoice envía por correo un enlace de pago en cada período (Pago Móvil, Débito Inmediato, tarjeta, stablecoins o pesos colombianos: lo que acepte tu cuenta). charge_automatically debita la cuenta bancaria del cliente por domiciliación: autoriza una sola vez y no vuelve a pagar a mano. Ver Débito automático.
  • Página de pago para cada factura. Cada factura tiene su propia página de pago; no tienes que construir pantallas de pago para las renovaciones.
  • Correos en español, a tu nombre. Facturas, recordatorios, recibos y avisos de cobro salen como “<tu negocio> vía VEX Pay”, y las respuestas llegan al correo de tu negocio.
  • Cobranza incluida. Cada factura tiene intentos de cobro diarios: el primero al emitirse y luego uno por día (3 en total por defecto, hasta 15). Si sigue sin pagar después del último, la suscripción se cancela… y se reactiva sola si el cliente paga esa factura tarde.
  • Períodos de prueba, cancelación al final del período, metadata y paginación por cursor, con una API parecida a las APIs de facturación que ya conoces.
Ideal paraEjemplo
MembresíasUn gimnasio que cobra $25 al mes
Clases y tutoríasUna academia que cobra $40 cada 4 semanas
Software y contenidoUn plan SaaS de $15 al mes o $150 al año
ServiciosLimpieza semanal, mantenimiento mensual, servicio de internet

Cómo funciona

ObjetoQué esId
ProductoLo que vendes (quizás ya lo tienes para tus enlaces de pago)UUID
PrecioCuánto y cada cuánto: un monto en USD y un intervalo (day, week, month, year) × intervalCountprice_…
ClienteQuién paga: nombre, correo y, opcionalmente, teléfono, cédula/RIF y bancocus_…
SuscripciónUn cliente en un precio, con su forma de cobro y su estadosub_…
FacturaLo que se debe por un período, con su página de pagoin_…
Sesión de autorizaciónUn enlace de 24 horas donde el cliente autoriza la domiciliaciónseti_…
Método de pagoLa domiciliación autorizada del clientepm_…
  1. Creas la suscripción

    El primer período empieza ahora (o al terminar la prueba). VEX Pay emite la primera factura de inmediato.

  2. Se cobra la factura

    Con send_invoice el cliente recibe un correo con el enlace de pago. Con charge_automatically VEX Pay debita su cuenta.

  3. Se paga

    La factura pasa a paid, el dinero entra a tu saldo como cualquier otro pago, el cliente recibe su recibo y tú recibes invoice.paid.

  4. Empieza el siguiente período

    Al terminar cada período empieza el siguiente y se emite una factura nueva. Los períodos se cuentan desde la fecha de inicio: una suscripción que empieza un día 31 se renueva el último día de los meses más cortos.

Inicio rápido: tu primera suscripción

Usa tu clave de prueba: no se cobra nada y los correos solo se entregan al correo de tu perfil de negocio (ver Modo de prueba más abajo). Llama a la API desde tu servidor con curl o con uno de los SDK.

1. Productobash
curl --request POST "${API_URL}/v1/products" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "name": "Plan Pro", "priceUsd": "25.00" }'
# → { "id": "PRODUCT_ID", ... }
2. Preciobash
curl --request POST "${API_URL}/v1/prices" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "product": "PRODUCT_ID",
    "unitAmountUsd": 25,
    "recurring": { "interval": "month", "intervalCount": 1 },
    "nickname": "Pro mensual"
  }'
# → { "id": "price_…", "unitAmountUsd": "25.00", "currency": "USD", ... }
3. Clientebash
curl --request POST "${API_URL}/v1/customers" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "name": "Ana Pérez",
    "email": "ana@example.com",
    "phone": "04141234567",
    "idNumber": "V12345678",
    "metadata": { "userId": "u_981" }
  }'
# → { "id": "cus_…", ... }
4. Suscripciónbash
curl --request POST "${API_URL}/v1/subscriptions" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "customer": "cus_…",
    "price": "price_…",
    "collectionMethod": "send_invoice"
  }'
Node SDKjavascript
const product = await vexpay.products.create({ name: 'Plan Pro', priceUsd: '25.00' });

const price = await vexpay.prices.create({
  product: product.id,
  unitAmountUsd: 25,
  recurring: { interval: 'month', intervalCount: 1 },
  nickname: 'Pro mensual',
});

const customer = await vexpay.customers.create({
  name: 'Ana Pérez',
  email: 'ana@example.com',
  metadata: { userId: 'u_981' },
});

const subscription = await vexpay.subscriptions.create({
  customer: customer.id,
  price: price.id,
  collectionMethod: 'send_invoice',
});
Python SDKpython
product = vexpay.products.create({"name": "Plan Pro", "priceUsd": "25.00"})

price = vexpay.prices.create({
    "product": product.id,
    "unitAmountUsd": 25,
    "recurring": {"interval": "month", "intervalCount": 1},
    "nickname": "Pro mensual",
})

customer = vexpay.customers.create({
    "name": "Ana Pérez",
    "email": "ana@example.com",
    "metadata": {"userId": "u_981"},
})

subscription = vexpay.subscriptions.create({
    "customer": customer.id,
    "price": price.id,
    "collectionMethod": "send_invoice",
})
PHP SDKphp
$product = $vexpay->products->create(['name' => 'Plan Pro', 'priceUsd' => '25.00']);

$price = $vexpay->prices->create([
    'product' => $product->id,
    'unitAmountUsd' => 25,
    'recurring' => ['interval' => 'month', 'intervalCount' => 1],
    'nickname' => 'Pro mensual',
]);

$customer = $vexpay->customers->create([
    'name' => 'Ana Pérez',
    'email' => 'ana@example.com',
    'metadata' => ['userId' => 'u_981'],
]);

$subscription = $vexpay->subscriptions->create([
    'customer' => $customer->id,
    'price' => $price->id,
    'collectionMethod' => 'send_invoice',
]);
Respuestajson
{
  "id": "sub_3fd84b2d9ad14f74a2d90f2f56984db5",
  "object": "subscription",
  "customer": "cus_…",
  "price": { "id": "price_…", "unitAmountUsd": "25.00", "currency": "USD", "recurring": { "interval": "month", "intervalCount": 1 }, ... },
  "status": "active",
  "collectionMethod": "send_invoice",
  "collectionAttempts": null,
  "paymentMethod": null,
  "currentPeriodStart": "2026-10-21T14:00:00.000Z",
  "currentPeriodEnd": "2026-11-21T14:00:00.000Z",
  "trialStart": null,
  "trialEnd": null,
  "cancelAtPeriodEnd": false,
  "canceledAt": null,
  "cancellationReason": null,
  "latestInvoice": "in_…",
  "methods": [],
  "metadata": {},
  "createdAt": "2026-10-21T14:00:00.000Z",
  "livemode": false
}

La primera factura ya existe: consúltala con GET /v1/invoices?subscription=sub_… y abre su hostedInvoiceUrl para ver lo mismo que ve tu cliente. En producción Ana también la recibe por correo; en modo de prueba el correo solo se entrega si el del cliente es el correo de tu perfil de negocio.

Precios

Un precio pertenece a un producto y fija el monto y el ciclo. Un mismo producto puede tener varios precios, por ejemplo mensual y anual.

CampoReglas
unitAmountUsdDólares, hasta 2 decimales, al menos el cobro mínimo de tu cuenta (si no, 422 amount_below_minimum) y como máximo 100.000.
recurring.intervalday, week, month o year.
recurring.intervalCountPor defecto 1. El ciclo completo puede durar como máximo un año: hasta 365 días, 52 semanas, 12 meses o 1 año. Cada 2 semanas es { "interval": "week", "intervalCount": 2 }.
nickname, metadataOpcionales; tus propias etiquetas (metadata: hasta 20 pares de texto).

El monto, el ciclo y el producto no cambian una vez creado el precio: intentar cambiarlos responde 400. Para subir un precio, crea uno nuevo y archiva el anterior con POST /v1/prices/:id y { "active": false }. Las suscripciones existentes conservan su precio; las nuevas no pueden usar un precio archivado (price_inactive).

Clientes

CampoPara qué sirve
emailObligatorio para send_invoice: ahí llegan las facturas. emailStatus pasa a undeliverable si el correo rebota; actualiza el correo para volver a intentarlo.
phone, idNumber, bankCodeOpcionales. Prellenan la página de autorización de la domiciliación para que el cliente escriba menos.
name, metadataSe ven en tu portal; usa metadata para guardar el id de usuario de tu sistema.

Actualiza con POST /v1/customers/:id (envía null para borrar un campo). DELETE /v1/customers/:id elimina a un cliente sin suscripciones vigentes; si las tiene, responde 409 customer_has_active_subscriptions. Busca un cliente por correo con GET /v1/customers?email=ana@example.com.

Suscripciones

CampoSignificado
customer, priceObligatorios. El precio debe estar activo.
collectionMethodsend_invoice (por defecto) o charge_automatically.
collectionAttemptsDe 1 a 15 intentos diarios por factura antes de cancelar la suscripción. Omítelo (o envía null) para usar el valor por defecto de tu cuenta: 3, salvo que lo cambies en Portal → Billing → Settings.
paymentMethodSolo para charge_automatically: el pm_… a debitar. Por defecto, la domiciliación más reciente del cliente que no esté inactiva.
trialPeriodDays o trialEndUn período de prueba gratis: de 1 a 730 días, o una fecha ISO hasta 730 días en el futuro. No se emite factura hasta que termine.
cancelAtPeriodEndtrue termina la suscripción al cerrar el período actual, sin factura nueva.
methodsQué métodos ofrece la página de pago de sus facturas: pago_movil, c2p, vpos, usdt, usdc, cop. Omítelo para ofrecer todos los que acepta tu cuenta.
metadataTus propios pares clave/valor.
EstadoSignificado
trialingEn período de prueba.
activeAl día, o su factura se emitió hace menos de un día.
past_dueTiene una factura sin pagar un día después de emitida. Los intentos de cobro continúan.
canceledTerminada. cancellationReason es requested (por ti o al final del período) o payment_failed (sin pago tras el último intento).
Para…Llamada
ListarGET /v1/subscriptions?customer=cus_…&status=past_due&limit=25: las más recientes primero; envía nextCursor como cursor para la página siguiente.
Cancelar al final del períodoPOST /v1/subscriptions/:id con { "cancelAtPeriodEnd": true } (envía false para deshacerlo).
Cancelar yaDELETE /v1/subscriptions/:id. Las facturas abiertas se anulan.
Cambiar la forma de cobroPOST /v1/subscriptions/:id con { "collectionMethod": "charge_automatically" } (o de vuelta a send_invoice, que necesita un cliente con correo). Aplica desde la próxima factura, con las mismas validaciones que al crearla, incluido el límite por cobro (Débito automático).
Cambiar los intentos o los métodosPOST /v1/subscriptions/:id con collectionAttempts o methods. Aplica desde la próxima factura.

Facturas y su página de pago

Cada período tiene una factura, numerada por cuenta y sin saltos (K7Q2M9XA-0001, -0002, …). Vence al emitirse. Su hostedInvoiceUrl es una página de pago a tu nombre: el cliente elige el método y paga el monto en Bs a la tasa de ese momento. Los correos muestran el monto solo en dólares, porque el monto en bolívares depende del día en que se pague.

EstadoSignificado¿Se puede pagar todavía?
openEsperando el pagoSí
paidPagada. Tiene paidAt, paymentId y billingFee—
uncollectibleSe dejó de cobrar (tras el último intento, o porque tú lo decidiste)Sí: un pago tardío sigue contando
voidAnulada; la página de pago se cierraNo
Para…Llamada
ListarGET /v1/invoices?subscription=sub_… (también customer, status, cursor, limit).
AnularPOST /v1/invoices/:id/void: solo una factura open.
Dejar de cobrarPOST /v1/invoices/:id/mark-uncollectible: la página sigue aceptando el pago.
Registrar un pago hecho fuera de VEX PayPOST /v1/invoices/:id/pay con { "paidOutOfBand": true } (efectivo, transferencia). No se mueve dinero ni se cobra comisión.
Reenviarla por correoPortal → Billing → Invoices → Resend, como máximo una vez cada 10 minutos.
  • attemptsTotal, attemptCount y nextPaymentAttempt indican en qué intento de cobro va la factura.
  • Cuando la factura se paga a través de VEX Pay, payment.completed incluye invoice: { id, number, subscription }, así tu manejador de pagos actual sabe a qué suscripción corresponde.

Cobro, reintentos y cancelación

Cada factura tiene N intentos de cobro, uno por día: el intento 1 en el momento en que se emite, el 2 un día después, y así sucesivamente. N es el collectionAttempts de la suscripción (de 1 a 15) o, si no lo tiene, el valor por defecto de tu cuenta (3, salvo que lo cambies). N queda fijo en la factura al emitirse (attemptsTotal), así que un cambio posterior aplica desde la próxima factura. En cada intento se envía la factura por correo (send_invoice) o se debita al cliente (charge_automatically; si no se puede hacer el débito, ese intento envía la factura por correo).

Cuándo (desde la emisión)Qué pasaWebhooks
Día 0Intento 1: correo “Factura … de <tu negocio>”, o el primer débito automáticoinvoice.created, invoice.payment_attempt
Día 1Sigue sin pagar → la factura está vencida y la suscripción pasa a past_due. Intento 2: correo de recordatorio o un nuevo débitoinvoice.overdue, customer.subscription.updated, invoice.payment_attempt
Días 2 … N−1Un intento por día. El último correo dice “Último aviso”invoice.payment_attempt
Día NSigue sin pagar → sus facturas abiertas pasan a uncollectible, la suscripción se cancela con cancellationReason: "payment_failed" y el cliente recibe el correo “Suscripción cancelada por falta de pago”, con un botón para pagar y reactivarlainvoice.marked_uncollectible, customer.subscription.deleted

Con el valor por defecto N = 3: intentos los días 0, 1 y 2, past_due el día 1 y cancelación el día 3. Con N = 1 la factura vence y la suscripción se cancela al mismo tiempo, un día después de la emisión. Si el cliente paga en cualquier momento, se detienen los intentos restantes, recibe su recibo y una suscripción past_due vuelve a active. Una suscripción past_due sigue renovándose: si el período termina con una factura pendiente, igual se emite la siguiente.

Cómo elegir N: 3 funciona bien para planes mensuales. Usa más (hasta 15) si tus clientes suelen pagar tarde, y menos para planes diarios o semanales. Configura el valor de tu cuenta en Portal → Billing → Settings, o por suscripción con collectionAttempts.

Correos a tus clientes

Todos los correos van en español, con el nombre y el logo de tu perfil de negocio, salen de facturas@mail.vexwallet.co como “<tu negocio> vía VEX Pay”, y las respuestas llegan al correo de tu negocio. Cada correo se envía una sola vez: los reintentos nunca generan duplicados.

CorreoCuándo
Factura … de <tu negocio>Se emite una factura (send_invoice), o no se pudo hacer un débito automático
Recordatorio / Último avisoEn cada intento siguiente de una factura sin pagar que no sea un débito exitoso, con “intento X de N”; el último es el aviso final antes de la cancelación
Pago recibidoSe paga una factura. En los débitos automáticos muestra los Bs debitados y la tasa
Aviso de cobro3 días antes de cada renovación con débito automático (si el método de pago todavía se puede debitar), con un estimado en Bs a la tasa de hoy
Suscripción cancelada por falta de pagoDespués del último intento, con un botón para pagar y reactivar

Los correos de factura y de recordatorio muestran el monto en dólares, el número de factura, la fecha en que se cancelará la suscripción si no se paga y el botón hacia la página de pago. Nunca muestran un monto fijo en Bs, porque se calcula cuando el cliente paga.

Períodos de prueba y avisos

  • Una prueba empieza con estado trialing y sin factura. Al terminar, empieza el primer período pagado y se emite su factura.
  • customer.subscription.trial_will_end llega 3 días antes de que termine la prueba (en pruebas de más de 3 días): buen momento para recordárselo al cliente.
  • invoice.upcoming llega 3 días antes de cada renovación que vaya a ocurrir, con el monto y el próximo período. Los clientes con débito automático reciben en ese momento el correo “Aviso de cobro”.

Comisiones y tu saldo

Una factura pagada a través de VEX Pay es un pago normal: aplica la comisión de su método (ver Precios) y el dinero sigue la retención habitual del saldo (ver Saldos y retenciones). Además, VEX Pay cobra una comisión de facturación del 0,5% de cada factura pagada (salvo que tu acuerdo diga otra cosa), en la moneda en que pagó el cliente. Aparece en la factura como billingFee y en los movimientos de tu saldo.

Ejemplo: plan de $25 pagado por Pago Móvil a Bs 100/USDMonto
El cliente pagaBs 2.500,00
Comisión de Pago Móvilsegún tus tarifas: ver Precios
Comisión de facturación (0,5%)Bs 12,50
Tú recibesBs 2.500,00 − comisión de Pago Móvil − Bs 12,50

Un débito automático (domiciliación) paga tu tarifa de cobros en bolívares, la misma de Débito Inmediato (ver Precios), más la comisión de facturación. Las facturas marcadas como pagadas fuera de VEX Pay (paidOutOfBand: true) no mueven dinero ni pagan comisión.

Webhooks

Suscribe tu endpoint a los eventos de facturación que necesites (ver Webhooks para el registro y la verificación de firmas). Salvo que se indique otra cosa, data es el objeto completo, el mismo JSON que devuelve la API, más livemode.

EventoCuándo`data`
customer.created / customer.updatedSe creó o actualizó un clienteCliente
customer.deletedSe eliminó un clienteCliente + deleted: true
customer.subscription.createdSe creó una suscripciónSuscripción
customer.subscription.updatedEmpezó un período nuevo, pasó a past_due o volvió a active, o cambiaste la cancelación al final del período, la forma de cobro o el método de pagoSuscripción
customer.subscription.deletedSe canceló: por ti, al final del período o por falta de pago (cancellationReason)Suscripción
customer.subscription.trial_will_end3 días antes de que termine la pruebaSuscripción
customer.subscription.reactivatedUn pago tardío reactivó una suscripción cancelada por falta de pagoSuscripción
invoice.createdSe emitió una facturaFactura
invoice.upcoming3 días antes de una renovaciónsubscription, customer, amountDueUsd, currency, renewsAt, periodStart, periodEnd
invoice.payment_attemptCada intento de cobroinvoice, attempt, attemptsTotal, nextPaymentAttempt, collectionMethod, attemptedAt
invoice.payment_failedSe rechazó un débito automático, o la factura supera el límite por cobro (en ese caso paymentId es null)invoice, attempt, attemptsTotal, nextPaymentAttempt, paymentId, failureCode, failureMessage
invoice.overdueSigue sin pagar un día después de emitidaFactura
invoice.paidPagada, a través de VEX Pay o marcada como pagada fuera de VEX PayFactura
invoice.voidedAnulada por ti o por una cancelación inmediataFactura
invoice.marked_uncollectibleMarcada como incobrable por ti o tras el último intentoFactura
payment_method.attachedUn cliente autorizó la domiciliaciónMétodo de pago (pending)
payment_method.updatedPasó a active (primer débito aceptado) o a inactive (un débito mostró que la autorización se canceló o se suspendió en el banco)Método de pago
payment_method.detachedLo desvinculasteMétodo de pago
Registrar eventos de facturaciónbash
curl --request POST "${API_URL}/v1/webhooks" \
  --header "content-type: application/json" \
  --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "url": "https://example.com/webhooks/vexpay",
    "events": [
      "customer.subscription.created", "customer.subscription.updated",
      "customer.subscription.deleted", "customer.subscription.reactivated",
      "invoice.paid", "invoice.payment_failed", "invoice.overdue",
      "payment_method.attached", "payment_method.updated"
    ]
  }'
invoice.paidjson
{
  "event": "invoice.paid",
  "data": {
    "id": "in_3fd84b2d9ad14f74a2d90f2f56984db5",
    "object": "invoice",
    "number": "K7Q2M9XA-0001",
    "status": "paid",
    "customer": "cus_…",
    "subscription": "sub_…",
    "collectionMethod": "send_invoice",
    "amountDueUsd": "25.00",
    "amountPaidUsd": "25.00",
    "currency": "USD",
    "periodStart": "2026-10-21T14:00:00.000Z",
    "periodEnd": "2026-11-21T14:00:00.000Z",
    "dueDate": "2026-10-21T14:00:00.000Z",
    "attemptsTotal": 3,
    "attemptCount": 1,
    "nextPaymentAttempt": null,
    "paidAt": "2026-10-21T15:12:40.000Z",
    "paidOutOfBand": false,
    "paymentId": "9b2c…",
    "billingFee": { "amount": "12.50", "currency": "VES", "percent": "0.5" },
    "hostedInvoiceUrl": "https://pay.vexwallet.co/pay/in-…",
    "createdAt": "2026-10-21T14:00:00.000Z",
    "livemode": true
  },
  "timestamp": "2026-10-21T15:12:41.000Z"
}

Modo de prueba

  • Todo lo anterior funciona con tu clave de prueba, con los mismos webhooks. Los objetos de prueba y de producción son independientes.
  • Los correos solo van al correo de tu perfil de negocio. En modo de prueba un correo solo se entrega si el correo del cliente es el de tu perfil de negocio; cualquier otra dirección se suprime. Usa esa dirección en tus clientes de prueba para ver exactamente lo que reciben tus clientes.
  • Paga las facturas de prueba en su página con los métodos de prueba.
  • El débito automático funciona en modo de prueba sin pedirle nada a VEX Pay: un simulador hace las veces del banco. Ver Débito automático para los teléfonos que simulan cada resultado.
  • Los relojes de prueba (test clocks) llegarán pronto. Mientras tanto, prueba renovaciones y cobranza con un precio diario ("recurring": { "interval": "day" }): una factura nueva cada 24 horas, y una sin pagar llega a past_due en un día y a la cancelación en N días.

Errores

Los errores tienen la forma habitual: { "error": "<código>", "message": "…" }, más field cuando un dato es inválido.

EstadoerrorSignificado
400invalid_dataFalta un campo o es inválido; ver field
400customer_email_requiredsend_invoice necesita un cliente con correo
400price_inactiveEl precio está archivado
400plan_changes_not_supportedNo se puede cambiar el precio de una suscripción
400subscription_canceledLa suscripción ya está cancelada
400invoice_not_openSolo una factura abierta se puede anular, marcar como incobrable o como pagada
400method_not_allowedLa domiciliación no está habilitada en tu cuenta
400automatic_charging_unavailableEl débito automático (y las sesiones de autorización) aún no están habilitados en producción. Nunca ocurre en modo de prueba
400amount_exceeds_limitcharge_automatically para un precio que supera Bs 1.500.000 a la tasa de hoy; el mensaje indica el máximo aproximado en USD. Usa send_invoice
400payment_method_requiredEl cliente todavía no ha autorizado la domiciliación
400payment_method_detachedEl método de pago fue desvinculado
404not_foundNo existe ese objeto en esta cuenta (o en este modo)
409customer_has_active_subscriptionsCancela las suscripciones del cliente antes de eliminarlo
422amount_below_minimumEl precio es menor que tu cobro mínimo

Los esquemas completos de solicitud y respuesta de cada endpoint de facturación están en la referencia de la API (Referencia de la API).