Webhook genérico: eventos, payload y firma

Recibe en tu servidor un POST en JSON por cada evento elegido, firmado con un secreto que solo tú y VSL Max conocen.

El webhook genérico envía a tu servidor un POST en JSON cada vez que ocurre uno de los eventos que elegiste. Es el camino para conectar VSL Max con un ERP, un área de miembros o cualquier sistema propio.

Cómo registrar un endpoint

  1. Abre Configuración › Integraciones y, en la tarjeta Webhook genérico, haz clic en Conectar.
  2. En Agregar endpoint, indica la URL del endpoint. Tiene que ser https, sin usuario ni contraseña en la URL, en el puerto estándar, 443 u 8443, y apuntar a una dirección pública. Las direcciones locales o de red privada se rechazan.
  3. Elige los eventos y haz clic en Agregar endpoint.
  4. Copia el secreto de firma que aparece en ese momento. Empieza con whsec_ y no vuelve a aparecer: después la pantalla muestra solo los últimos caracteres. Si lo pierdes, genera otro.

Solo los administradores crean y modifican endpoints. La cuenta acepta hasta 10 endpoints.

Eventos disponibles

  • sale.approved: venta aprobada.
  • sale.refunded: reembolso.
  • sale.chargeback: contracargo.
  • sale.canceled: venta cancelada.
  • video.ready: un video terminó de procesarse y está listo.
  • balance.low: se disparó el aviso de saldo de plays bajo (el mismo disparador del correo).
  • organization.suspended: la cuenta fue suspendida.

Si no eliges nada, el endpoint recibe los cuatro eventos de venta. El botón Probar envía un test.ping.

Formato de la solicitud

Cada entrega lleva estos encabezados:

  • Content-Type: application/json y User-Agent: VSLMax-Webhooks/1.0.
  • X-VSLMax-Event: el tipo de evento, el mismo del campo type del cuerpo.
  • X-VSLMax-Delivery: el id de la entrega, igual en todos los intentos.
  • X-VSLMax-Signature: con el formato t=<hora unix>,v1=<firma>.

Ejemplo de cuerpo de una venta aprobada:

{
  "id": "evt_3f2a9c1e-8b7d-4e21-9a55-0c1d2e3f4a5b",
  "type": "sale.approved",
  "createdAt": "2026-09-23T15:04:05.000Z",
  "data": {
    "sale": {
      "id": "3f2a9c1e-8b7d-4e21-9a55-0c1d2e3f4a5b",
      "externalId": "HP123456",
      "status": "approved",
      "amount": 197,
      "amountCents": 19700,
      "currency": "USD",
      "occurredAt": "2026-09-23T15:03:58.000Z"
    },
    "conversion": { "id": "…", "name": "Hotmart principal", "platform": "hotmart" },
    "video": { "id": "…", "title": "VSL v3" },
    "attribution": "key",
    "visitorId": "…"
  }
}

En reembolso y contracargo, amount y amountCents vienen negativos. video es null cuando la venta no se atribuyó a ningún video, y attribution indica cómo se asoció al video (none cuando no se asoció).

En los demás eventos, data trae:

  • video.ready: video con id, title y durationSeconds.
  • balance.low: balance con el aviso, los plays incluidos, usados, restantes y el inicio y fin del ciclo.
  • organization.suspended: organization con id, status y el motivo.
  • test.ping: un mensaje de prueba.

Cómo verificar la firma

Verifica la firma antes de confiar en el cuerpo:

  1. Separa t y v1 del encabezado X-VSLMax-Signature.
  2. Calcula el HMAC-SHA256, con tu secreto, del texto formado por t, un punto y el cuerpo sin procesar de la solicitud (antes de convertir el JSON).
  3. Compara el resultado en hexadecimal con v1, en tiempo constante.
  4. Rechaza si t está a más de 5 minutos de tu reloj. Así evitas el reenvío de una solicitud capturada.
  5. Usa el id del evento para ignorar repeticiones: el reintento envía el mismo id.

Ejemplo en Node.js:

import crypto from 'node:crypto'

function firmaValida(rawBody, header, secreto) {
  const partes = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(partes.t)
  if (Math.abs(Date.now() / 1000 - t) > 300) return false
  const esperado = crypto.createHmac('sha256', secreto).update(`${t}.${rawBody}`).digest('hex')
  const a = Buffer.from(esperado)
  const b = Buffer.from(partes.v1 ?? '')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

Respuestas y reintentos

Responde con cualquier estado 2xx en hasta 10 segundos. Cualquier otra respuesta, o la falta de respuesta, cuenta como fallo. Las redirecciones no se siguen.

Después de un fallo, volvemos a intentar en 1 minuto, 5 minutos y luego cada 15 minutos, hasta 6 intentos en total. Si todos fallan, la entrega queda como fallida en el historial del endpoint y la tarjeta muestra cuántos fallos seguidos hubo.

Pausar, probar y cambiar el secreto

  • Probar envía un test.ping en el momento y muestra si se entregó, el estado HTTP y el tiempo de respuesta.
  • Últimas entregas lista las entregas recientes del endpoint, con estado y número de intentos.
  • Pausar suspende los envíos sin borrar el endpoint. Reanudar vuelve a enviar los eventos nuevos.
  • Generar nuevo secreto cambia el secreto en el acto: las próximas entregas ya salen firmadas con el nuevo. Actualiza tu servidor enseguida.
  • Eliminar endpoint pide tu contraseña y detiene las entregas de inmediato. No se puede deshacer.