vexorpayDocs
vexorpayDocs
APIDocumentación de vexorpayPrimeros pasosVenta de producto digitalCómo funcionan los cobros

Core API

Referencia de APICrear link de pago únicoCrear link de pago recurrenteListar y obtener linksSuscripcionesCheckout por compraVentasClientesDisputasSaldoRetirosEventos de reconciliaciónCuentas conectadas (marketplace)Webhooks salientesAutenticación

Recursos

vexorpay MCPEspecificación OpenAPIMigrar desde el SDK `vexor`PostmanComunidadModo sandbox

Webhooks salientes

Recibe notificaciones firmadas cuando ocurren eventos de negocio en tu cuenta.

vexorpay te notifica por HTTP cuando pasan eventos de negocio en tu cuenta. Configuras los webhooks desde Configuración → Webhooks en el dashboard: creas un endpoint con la URL de tu servidor, eliges a qué eventos suscribirte y vexorpay genera un secreto de firma (mostrado una sola vez).

Eventos

EventoCuándo ocurredata.object
payment.completedSe cobró una venta: un pago único, el alta de una suscripción (incluida una prueba gratis, de USD 0) o una renovación.Venta
payment.refundedSe reembolsó una venta, total o parcialmente.Venta
subscription.createdSe creó una suscripción nueva sobre un link recurrente.Suscripción
subscription.updatedCambió una suscripción: estado (por ejemplo past_due por un cobro fallido, o canceled al terminar una baja ya avisada) o ciclo.Suscripción
subscription.canceledSe dio de baja una suscripción: al programarse la cancelación al fin del ciclo o, si no se programó, cuando termina. Se envía una sola vez.Suscripción
dispute.createdSe abrió una disputa (o una alerta previa) sobre una venta.Disputa
dispute.updatedCambió una disputa (por ejemplo, se resolvió como won o lost).Disputa

Puedes suscribir cada endpoint a todos o a un subconjunto de estos eventos.

Entrega

  • Cada evento se envía por POST al endpoint, con Content-Type: application/json.
  • Sin confirmación 2xx, vexorpay reintenta con backoff (~5 min, ~30 min, ~3 h) hasta 4 intentos y lo marca como fallido. En Configuración → Webhooks ves el log de entregas (intentos, estado, último error).
  • Un mismo evento enviado a varios endpoints y en varios intentos comparte el mismo evt_ (id idempotente para el receptor).

Cuerpo del evento

{
  "id": "evt_6c2314a4fb4a408c07c2",
  "type": "payment.completed",
  "created": "2026-01-02T12:05:00.000Z",
  "sandbox": false,
  "data": {
    "object": {
      "id": "67f1c1e2c8a4b0d1f2e3a4b5",
      "paymentLinkId": "66f1c1e2c8a4b0d1f2e3a4b5",
      "subscriptionId": null,
      "checkoutId": "ch_4f8a2c9d1e7b",
      "reference": "ORD-1001",
      "metadata": null,
      "status": "paid",
      "billingReason": "one_time",
      "currency": "usd",
      "subtotalCents": 1900,
      "taxCents": 0,
      "totalCents": 1900,
      "feeCents": 190,
      "feeBreakdown": {
        "vexorpay": { "amountCents": 151, "percent": 2.7, "fixedCents": 100 },
        "extras": [
          { "type": "cross_border", "amountCents": 29, "percent": 1.5 },
          { "type": "fraud_prevention", "amountCents": 7, "percent": null },
          { "type": "authentication", "amountCents": 3, "percent": null }
        ]
      },
      "netCents": 1710,
      "refundedCents": 0,
      "refundableCents": 1900,
      "customerEmail": "cliente@mail.com",
      "customerName": "Ana Pérez",
      "productName": "Ebook UX en 30 días",
      "sandbox": false,
      "createdAt": "2026-01-02T12:05:00.000Z"
    }
  }
}

data.object es el recurso completo, con la misma forma que en la API: la venta en payment.* (su id es el de GET /api/v1/orders/{id} y reference es tu referencia de esa venta; ver Identificar cada venta), la suscripción en subscription.* y la disputa en dispute.*. En payment.refunded, refundedCents trae el total reembolsado hasta ese momento. sandbox viene a nivel raíz y dentro del objeto.

Cada renovación de una suscripción llega como payment.completed con billingReason: "subscription_cycle" y subscriptionId; un cobro fallido de renovación llega como subscription.updated con status: "past_due".

Eventos de suscripción

{
  "id": "evt_9a1b2c3d4e5f6a7b8c9d",
  "type": "subscription.created",
  "created": "2026-01-02T12:00:00.000Z",
  "sandbox": false,
  "data": {
    "object": {
      "id": "66f1c1e2c8a4b0d1f2e3a4b7",
      "paymentLinkId": "66f1c1e2c8a4b0d1f2e3a4b6",
      "priceId": "66f1c1e2c8a4b0d1f2e3a4c1",
      "status": "trialing",
      "amountCents": 900,
      "interval": "month",
      "intervalCount": 1,
      "currentPeriodStart": "2026-01-02T12:00:00.000Z",
      "currentPeriodEnd": "2026-01-09T12:00:00.000Z",
      "cancelAtPeriodEnd": false,
      "canceledAt": null,
      "customerEmail": "cliente@mail.com",
      "manageUrl": "https://…",
      "sandbox": false,
      "createdAt": "2026-01-02T12:00:00.000Z"
    }
  }
}

Verificación de firma

Cada request incluye:

HeaderValor
vexorpay-SignatureHMAC-SHA256 del cuerpo crudo (exactamente como llega) con tu secreto, en hex.
vexorpay-Event-IdId del evento (evt_...), igual al id del body.

Ejemplo

import { createHmac, timingSafeEqual } from 'node:crypto'

async function verify(req, secret) {
  const raw = await text(req)              // cuerpo crudo, SIN parsear
  const sig = req.headers['vexorpay-signature']
  const expected = createHmac('sha256', secret).update(raw).digest('hex')
  const a = Buffer.from(sig ?? '', 'hex')
  const b = Buffer.from(expected, 'hex')
  const ok = a.length === b.length && timingSafeEqual(a, b)
  if (ok) req.event = JSON.parse(raw)      // recién acá parseás
}

// Uso:
// 1) verificás la firma
// 2) deduplicás por body.id (guardás el evt_ procesado)
// 3) recién entonces procesás y respondés 2xx
  • Nunca confíes en un POST sin firma válida.
  • Compara en tiempo constante (timingSafeEqual).
  • Parsea el body solo después de verificar; el hash usa el body crudo.

Verificación a demanda (reconciliación)

Si perdiste un webhook, puedes consultar el mismo evento en el histórico:

GET /v1/events

Cada evento del histórico tiene el mismo evt_ que se envía por webhook (mismos id, type y data) → puedes deduplicar y re-sincronizar lo que faltó.

Rotación y seguridad

  • El secreto solo se muestra una vez al crearlo; si lo pierdes, rótalo desde el dashboard (el endpoint deja de funcionar hasta que actualices tu verificación).
  • Puedes pausar un endpoint sin borrarlo (no recibe eventos mientras está pausado).
  • Guarda el secreto como variable de entorno, nunca en el frontend ni en repositorios.

Cuentas conectadas (marketplace)

Opera en nombre de usuarios conectados a tu app con el header Vexor-Account y gestiona las conexiones.

Autenticación

Cómo autenticarte en la API de vexorpay con tu clave.

On this page

EventosEntregaCuerpo del eventoEventos de suscripciónVerificación de firmaEjemploVerificación a demanda (reconciliación)Rotación y seguridad