VipterCentro de Ayuda
Integraciones

Recibir eventos en tu sistema (webhooks)

Recibe en tu servidor un aviso firmado por cada venta, reembolso, suscripción o cliente nuevo, con reintentos automáticos e historial de entregas.

Admin o PropietarioTodos los planesVerificado el 28 sept 2026

Un webhook es una dirección de tu sistema a la que Vipter llama cuando algo pasa en la tienda: una venta aprobada, un reembolso, una suscripción cancelada. Cada llamada es un POST con los datos en JSON y una firma que prueba que vino de Vipter. Esta página es para quien va a programar la recepción, o para que se la pases a esa persona.

Antes de empezar

  • Una dirección https:// en tu sistema que acepte POST y responda con un código 2xx en hasta 10 segundos.
  • Rol Admin o Propietario en el proyecto de Vipter.

Paso 1: agregar el endpoint

  1. En el panel de Vipter, abre GeneralIntegracionesAutomatizacionesWebhooks.
  2. Haz clic en Agregar endpoint.
  3. Completa los campos numerados:
Formulario Agregar endpoint con URL, descripción y la lista de tipos de evento numerados
#CampoQué pegar
1URL del endpointObligatorioLa dirección que va a recibir los eventos. Tiene que empezar con https://.
2DescripciónOpcionalUn recordatorio para ti, como "ERP de la tienda". Hasta 200 caracteres.
3Tipos de eventoOpcionalMarca solo los eventos que usa tu sistema. Sin ninguno marcado, el endpoint recibe todos, incluso los que se creen en el futuro.
  1. Haz clic en Crear endpoint.

Los eventos de un endpoint no se pueden editar después. Para cambiar la lista, crea un endpoint nuevo y quita el anterior.

Paso 2: guardar el secreto de firma

Justo después de crearlo, Vipter muestra Tu secreto de firma: un valor que empieza con whsec_. Guárdalo ahora. Solo se muestra una vez; puedes rotarlo después.

Copia el secreto y guárdalo en tu sistema, por ejemplo en una variable de entorno. Después de cerrar la ventana, el endpoint solo muestra el final del secreto.

Trata el secreto como una contraseña

Quien tiene el secreto puede falsificar eventos que pasan la verificación. No pongas el secreto en el código fuente ni lo envíes por correo. Si se filtra, genera otro con el botón Rotar secreto.

Paso 3: verificar la firma

Cada llamada trae estos encabezados:

EncabezadoContenido
Vipter-Signaturet=<fecha en segundos Unix>,v1=<firma>. Durante la rotación del secreto, viene un segundo v1=.
Vipter-Event-IdEl ID del evento, como evt_….
Vipter-Event-TypeEl tipo del evento, como order.paid.
User-AgentVipter-Webhooks/1.0

La firma es un HMAC SHA-256, en hexadecimal, calculado con tu secreto sobre el texto <t>.<cuerpo>: el valor de t, un punto y el cuerpo de la solicitud exactamente como llegó. Para comprobarla:

  1. Lee el cuerpo sin procesar, antes de cualquier conversión a JSON. Un cuerpo reformateado genera otra firma.
  2. Recalcula el HMAC con tu secreto y compáralo con cada v1= del encabezado. Basta con que uno coincida.
  3. Rechaza las llamadas con un t demasiado antiguo. El ejemplo de abajo acepta hasta 5 minutos de diferencia.

El mismo código aparece en el panel, en Fragmento de verificación (Node.js):

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

export function verifyVipterSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return header.split(',').filter((p) => p.startsWith('v1=')).some((p) => {
    const given = Buffer.from(p.slice(3), 'hex');
    return given.length === expected.length / 2 && timingSafeEqual(given, Buffer.from(expected, 'hex'));
  });
}

Un ejemplo con Express, que recibe el cuerpo sin procesar y responde enseguida:

import express from 'express';

const app = express();

app.post('/webhooks/vipter', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyVipterSignature(raw, req.get('Vipter-Signature') ?? '', process.env.VIPTER_WEBHOOK_SECRET)) {
    return res.status(400).send('firma inválida');
  }
  const event = JSON.parse(raw);
  // Guarda event.id e ignóralo si ya se procesó.
  res.sendStatus(200);
  // Procesa event.type y event.data.object después de responder.
});

Paso 4: enviar un evento de prueba

  1. Con el endpoint activo, haz clic en Enviar evento de prueba, arriba en la página.
  2. Aparece Evento de prueba en cola. y la entrega aparece en Entregas recientes.

La prueba es un evento customer.created con un cliente ficticio y "test": true dentro de data.object. Solo llega a los endpoints activos que reciben customer.created o todos los eventos.

Funcionó si

La entrega aparece en Entregas recientes con el estado succeeded y el código HTTP que respondió tu sistema, y tu sistema aceptó la firma.

El formato de los eventos

Todos los eventos tienen el mismo sobre. El objeto en data.object depende del tipo.

{
  "id": "evt_4f1c2a9b0d3e5f6a7b8c9d0e",
  "object": "event",
  "type": "order.paid",
  "created": 1790000000,
  "livemode": true,
  "api_version": "2026-09-01",
  "data": {
    "object": {
      "object": "order",
      "id": "…",
      "status": "…",
      "total_amount": 19700,
      "currency": "BRL"
    }
  }
}
  • id es único por evento. Úsalo para ignorar repeticiones: una entrega reenviada llega con el mismo id.
  • Los montos de dinero vienen en centavos, como 19700 para R$ 197,00.
  • Pedido ("object": "order"): id, customer_id, customer_email, subscription_id, status, total_amount, currency, refunded_amount, order_type, recurrence, offer_id, payment_method, provider_slug, paid_at, items, external_order_id, status_source, created_at, updated_at. En order.paid, también downloads, con los enlaces de descarga del comprador cuando el producto tiene archivos.
  • Suscripción ("object": "subscription"): id, customer_id, customer_email, customer_name, status, current_offer_id, offer_name, product_id, product_name, billing_cycle, currency, current_amount, current_period_start, current_period_end, next_billing_at, trial_start, trial_end, cycles_completed, cycle_limit, cancel_at_period_end, cancelled_at, cancellation_reason, payment_instrument_id, created_at, updated_at.
  • Cliente ("object": "customer"): id, email, name, phone, document_type, metadata, created_at, updated_at.
  • Checkout abandonado ("object": "checkout_abandonment"): el comprador (customer), la oferta, el producto, la cantidad y el monto.

Lista de eventos

EventoCuándo se envía
order.paidSe aprobó un pago. También vale para las renovaciones de suscripción, con recurrence: "subsequent", y para las ventas marcadas como pagadas por el equipo, con status_source: "manual".
order.failedSe rechazó un cobro.
order.refundedSe reembolsó un pedido.
order.partially_refundedSe reembolsó parte de un pedido.
order.charged_backEl comprador disputó la compra con su banco (contracargo).
subscription.createdSe creó una suscripción.
subscription.renewedSe renovó una suscripción.
subscription.dunningEl cobro de la renovación falló y la suscripción está en morosidad.
subscription.reactivatedUna suscripción volvió a estar activa.
subscription.upgradedEl suscriptor cambió a un plan más caro.
subscription.downgradedEl suscriptor cambió a un plan más barato.
subscription.payment_method_changedSe cambió la tarjeta de la suscripción.
subscription.pausedSe pausó la suscripción.
subscription.resumedLa suscripción pausada se reanudó.
subscription.cancelledSe canceló la suscripción.
subscription.expiredLa suscripción terminó.
customer.createdSe registró un cliente nuevo.
customer.updatedCambiaron los datos de un cliente.
checkout.abandonedEl comprador completó los datos en el checkout y no pagó. Se envía después de un tiempo sin actividad, y solo si no compró en el intervalo.
checkout.recoveredUn checkout abandonado terminó en compra.

Entregas y reintentos

Una entrega funciona cuando tu sistema responde con un código 2xx en hasta 10 segundos. Cualquier otra respuesta, incluidas las redirecciones, o la falta de respuesta cuenta como fallo.

Después de un fallo, Vipter lo intenta de nuevo, hasta 8 intentos en total. La espera entre ellos es de 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas, 24 horas y 48 horas. Después del octavo intento, la entrega queda con el estado exhausted.

En Entregas recientes, cada fila muestra el Evento, el Estado con el código HTTP y el error, los Intentos y el Último intento. Las entregas que no funcionaron tienen el botón Reenviar, que reinicia los intentos desde cero.

Endpoint desactivado automáticamente

Si 20 entregas seguidas agotan los intentos, Vipter desactiva el endpoint y muestra el motivo en su tarjeta. Una entrega exitosa pone ese conteo en cero. Corrige tu sistema y vuelve a encender el endpoint con el interruptor Activo.

Mientras un endpoint está desactivado, los eventos nuevos no se guardan para él. Usa la lista de pedidos y de suscripciones del panel para revisar qué pasó en ese período.

Rotar el secreto o quitar el endpoint

  • Rotar secreto: genera un secreto nuevo y lo muestra una sola vez. ¿Rotar el secreto? El anterior sigue válido por 24 horas. En ese período, cada llamada trae las dos firmas, y tu sistema puede cambiar el secreto sin perder eventos.
  • Quitar: ¿Quitar este endpoint y su historial de entregas?

Problemas comunes

  • Ingresa una URL https://

    La dirección no empieza con https:// o tiene un error de escritura. No se aceptan direcciones http://.

  • La firma nunca coincide

    El cuerpo se convirtió a JSON antes de la verificación, o el secreto es de otro endpoint. Verifica el cuerpo sin procesar y revisa el final del secreto en la tarjeta del endpoint.

  • Las entregas fallan con timeout

    Tu sistema tarda más de 10 segundos en responder. Responde 200 en cuanto valides la firma y procesa el evento después.

  • El botón de prueba está deshabilitado

    No hay ningún endpoint activo. Enciende el interruptor Activo de un endpoint.

Qué hacer después

En esta página