Webhooks

View as Markdown

Los webhooks te permiten recibir notificaciones HTTP en tiempo real cuando ocurren eventos en tu cuenta de Wisboo. Cuando un evento se dispara, Wisboo envía una solicitud POST con un cuerpo JSON a la URL que hayas configurado.

Las solicitudes se entregan de forma asíncrona. Si tu endpoint retorna un código de estado que no sea 2xx, la entrega se reintenta hasta 5 veces con el siguiente esquema:

Nro. reintentoTiempo
110 segundos
21 minuto
310 minutos
41 hora
53 horas

Formato de la solicitud

Cada solicitud de webhook es un POST con Content-Type: application/json. El cuerpo siempre tiene la siguiente estructura:

1{
2 "id": "EVT-abc123",
3 "event_type": "product.sold",
4 "created_at": "2024-11-14T10:00:00Z",
5 "data": { ... }
6}
CampoTipoDescripción
idstringID único del evento, con prefijo EVT-.
event_typestringEl evento que disparó la entrega (ver Eventos soportados).
created_atstringTimestamp ISO 8601 de cuándo se creó el evento.
dataobjectPayload específico del evento (ver la sección de cada evento más abajo).

Validación de la firma

Cada solicitud incluye un header Wisboo-Signature. Verificarlo confirma que la solicitud provino de Wisboo y que el cuerpo no fue alterado.

Formato del header

Wisboo-Signature: t=1700000000,v1=5257a869...
  • t — Timestamp Unix (en segundos) de cuándo se envió la solicitud.
  • v1 — Firma HMAC-SHA256 del payload firmado.

Pasos de verificación

  1. Extraer t y v1 del header.
  2. Construir el payload firmado concatenando el timestamp, un . literal y el cuerpo crudo de la solicitud:
    signed_payload = t + "." + raw_body
  3. Calcular un HMAC-SHA256 usando el secreto de tu endpoint y el payload firmado:
    expected = HMAC-SHA256(secret, signed_payload)
  4. Comparar expected contra v1 usando una comparación en tiempo constante para prevenir timing attacks.
  5. Rechazar la solicitud si no coinciden, o si t está muy alejado en el pasado (tolerancia recomendada: 5 minutos).

El secreto del endpoint comienza con whsec_ y se muestra una única vez al crear el endpoint.

Ejemplo (Ruby)

1def valid_signature?(header, raw_body, secret, tolerance: 300)
2 parts = header.split(',').map { |p| p.split('=', 2) }.to_h
3 timestamp = parts['t'].to_i
4 signature = parts['v1']
5
6 return false if (Time.current.to_i - timestamp).abs > tolerance
7
8 expected = OpenSSL::HMAC.hexdigest('SHA256', secret, "#{timestamp}.#{raw_body}")
9 ActiveSupport::SecurityUtils.secure_compare(expected, signature)
10end

Ejemplo (Node.js)

1const crypto = require('crypto');
2
3function isValidSignature(header, rawBody, secret, toleranceSecs = 300) {
4 const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
5 const timestamp = parseInt(parts.t, 10);
6 const signature = parts.v1;
7
8 if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSecs) return false;
9
10 const expected = crypto
11 .createHmac('sha256', secret)
12 .update(`${timestamp}.${rawBody}`)
13 .digest('hex');
14
15 return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
16}

Importante: siempre calcular el HMAC sobre los bytes crudos del cuerpo de la solicitud, antes de cualquier parseo de JSON.


Eventos soportados

EventoDescripción
user.createdSe registró un nuevo usuario.
product.soldSe realizó la compra de un producto.
product.access_grantedSe otorgó acceso a un producto a un usuario.
payment.succeededSe procesó exitosamente un pago.
course.completedUn usuario completó un curso.

Payloads por evento

user.created

Se dispara cuando un nuevo usuario se registra.

CampoTipoDescripción
idstringID del usuario.
namestringNombre.
last_namestringApellido.
emailstringDirección de email.
countrystringCódigo de país ISO 3166-1 alpha-2. null si no fue provisto.
phonestringNúmero de teléfono. null si no fue provisto.
external_idstringTu propio identificador para este usuario, si fue definido. null en caso contrario.
created_atstringTimestamp ISO 8601.
custom_fieldsobjectMapa clave-valor de campos adicionales recolectados en el registro. phone y country se excluyen de este mapa (aparecen como campos de primer nivel).

Ejemplo

1{
2 "id": "EVT-abc123",
3 "event_type": "user.created",
4 "created_at": "2024-11-14T10:00:00Z",
5 "data": {
6 "id": "USR-abc123",
7 "name": "Jane",
8 "last_name": "Doe",
9 "email": "jane@example.com",
10 "country": "AR",
11 "phone": "+5491123456789",
12 "external_id": null,
13 "created_at": "2024-11-14T10:00:00Z",
14 "custom_fields": {
15 "company": "Acme"
16 }
17 }
18}

product.sold

Se dispara cuando se completa la compra de un producto.

Campos principales

CampoTipoDescripción
idstringID de la compra.
statusstringEstado de la compra. Valores posibles: pending (esperando el pago), paid (Compra completada), canceled (Compra para la cual ya no es posible intentar completar el pago), refunded (Reembolsada en su totalidad), failed (a la espera de procesar el pago, con intentos fallidos), rejected, in_progress(Compra con un plan de pago en cuotas vigente)
payment_idstringID del último pago asociado. null si no hay ninguno.
payment_gatewaystringGateway utilizado, valores posiblesstripe, mercadopago, wisboo_pay, payu, paypal, offline (el pago se realiza fuera de la plataforma), none (se aplicó un cupón del 100%).
base_pricenumberPrecio de lista original.
promotional_pricenumberPrecio tras la promoción, antes de otros ajustes.
promotion_percentage_offnumberDescuento de promoción como porcentaje (0–100).
promotion_amount_offnumberDescuento de promoción como monto fijo.
promotion_typestringTipo de promoción aplicada. null si no aplica. Posible valores: hotsale, popup, gift, two_for_one
discounted_amountnumberMonto total descontado.
extra_chargenumberCargo adicional aplicado.
extra_charge_percentagenumberCargo adicional como porcentaje.
extra_charge_namestringEtiqueta del cargo adicional. null si no aplica.
taxes_amountnumberMonto de impuestos.
total_amountnumberMonto final cobrado al comprador.
currencystringCódigo de moneda ISO 4217 de la transacción.
installmentsintegerCantidad de cuotas. 1 para pago único.
installment_plan_intervalstringIntervalo entre cuotas. null para pagos únicos. Posible valores: daily, weekly, biweekly, monthly, yearly. Solo aplicable para transacciones de Wisboo Pay.
installment_amountnumberMonto de la cuota. Solo aplicable para transacciones de Wisboo Pay.
installment_interest_ratenumberTasa de interés aplicada por cuota. Solo aplicable para transacciones de Wisboo Pay.
installment_interest_rate_amountnumberMonto de interés por cuota. Solo aplicable para transacciones de Wisboo Pay.
installment_plan_end_datestringFecha ISO 8601 en que finaliza el plan de cuotas. null para pagos únicos. Solo aplicable para transacciones de Wisboo Pay.
charged_installmentsnumberCantidad de cuotas pagadas hasta la fecha. null para pagos únicos. Solo
aplicable para transacciones de Wisboo Pay.
amount_duenumberMonto pendiente por pagar. Será 0 para compras pagadas en su totalidad.
amount_paidnumberMonto pagado hasta la fecha del total de la transacción.
next_charge_atstringTimestamp ISO 8601 de la fecha en la cual se va a cobrar la siguiente cuota, solo para compras con un plan de cuotas a través de Wisboo Pay.
gift_recipient_idstringID del usuario recipiente de una compra hecha por promoción de regalo (gift) o 2x1 (two_for_one).
gift_recipient_emailstringEmail del usuario recipiente de una compra hecha por promoción de regalo (gift) o 2x1 (two_for_one).
custom_fields objectMapa clave-valor de campos adicionales recolectados posterior a la compra. Estos campos corresponden a los definidos a nivel de producto, no a los atributos de los usuarios.
created_atstringTimestamp ISO 8601 de la compra.
paid_atstringTimestamp ISO 8601 de cuándo se confirmó el pago. null si aún no fue pagado.

Objeto discount

null cuando no se aplicó ningún código de descuento.

CampoTipoDescripción
percentagenumberPorcentaje de descuento.
discounted_codestringEl código promocional.
discounted_code_idstringID del código promocional.

Objeto product

CampoTipoDescripción
idstringID del producto.
namestringNombre del producto.
typestringTipo de producto (ej. course, digital_product).
slugstringSlug de la URL.

Objeto user

CampoTipoDescripción
idstringID del comprador.
namestringNombre.
last_namestringApellido.
emailstringDirección de email.
external_idstringTu identificador para este usuario. null si no fue definido.

Objeto checkout_session

CampoTipoDescripción
idstringID de la sesión de checkout.
started_atstringTimestamp ISO 8601 de inicio de la sesión.
ipstringDirección IP del comprador.
device_typestringTipo de dispositivo (ej. desktop, mobile).
user_agentstringUser agent del navegador.
utm_sourcestringUTM source. null si no está presente.
utm_mediumstringUTM medium. null si no está presente.
utm_campaignstringUTM campaign. null si no está presente.
utm_termstringUTM term. null si no está presente.
utm_contentstringUTM content. null si no está presente.

Ejemplo

1{
2 "id": "EVT-abc123",
3 "event_type": "product.sold",
4 "created_at": "2024-11-14T10:00:00Z",
5 "data": {
6 "id": "RXPXX0P3",
7 "status": "paid",
8 "payment_id": "PAY-NCOA076X1EK84YCX",
9 "payment_gateway": "mercadopago",
10 "base_price": 10000,
11 "promotion_percentage_off": 0,
12 "promotion_amount_off": 0,
13 "promotional_price": 10000,
14 "discounted_amount": 0,
15 "extra_charge": 0,
16 "extra_charge_percentage": 0,
17 "extra_charge_name": null,
18 "taxes_amount": 0,
19 "installment_plan_interval": null,
20 "installments": 1,
21 "installment_amount": 0,
22 "installment_interest_rate": 0,
23 "installment_interest_rate_amount": 0,
24 "amount_due": 0,
25 "amount_paid": 10000,
26 "charged_installments": 1,
27 "installment_plan_end_date": null,
28 "next_charge_at": null,
29 "total_amount": 10000,
30 "currency": "ARS",
31 "gift_recipient_id": null,
32 "gift_recipient_email": null,
33 "custom_fields":{
34 "extra_request": "Extra services"
35 },
36 "promotion_type": null,
37 "checkout_session": {
38 "id": "CHS-97SBU3N4VAZRMOPU",
39 "started_at": "2026-08-07T14:15:06Z",
40 "ip": "192.230.91.1",
41 "device_type": "desktop",
42 "user_agent":
43 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, li
44 ke Gecko) Chrome/149.0.0.0 Safari/537.36",
45 "utm_medium": null,
46 "utm_source": null,
47 "utm_campaign": null,
48 "utm_term": null,
49 "utm_content": null
50 },
51 "discount": null,
52 "product": {
53 "id": "PRO-019fdc57046578ce86071d30fc541",
54 "name": "Curso",
55 "type": "course_type",
56 "slug": "curso"
57 },
58 "user": {
59 "id":"USR-06a75e87a2bf7000b497bbd3797955aa",
60 "name": "Jane",
61 "last_name": "Doe",
62 "email": "jane@example.com",
63 "external_id": null,
64 }
65 }
66}

product.access_granted

Se dispara cuando un usuario obtiene acceso a un producto, independientemente del motivo (compra, asignación manual, inscripción gratuita, etc.).

CampoTipoDescripción
idstringID del registro de acceso.
progressnumberProgreso de completitud de 0 a 100.
accessiblebooleanSi el usuario tiene acceso activo actualmente.
access_due_datestringDatetime ISO 8601 de vencimiento del acceso. null si es ilimitado.
created_atstringTimestamp ISO 8601 de cuándo se otorgó el acceso.

Objeto product

CampoTipoDescripción
idstringID del producto.
slugstringSlug de la URL.
namestringNombre del producto.

Objeto user

CampoTipoDescripción
idstringID del usuario.
namestringNombre.
last_namestringApellido.
emailstringDirección de email.
external_idstringTu identificador para este usuario. null si no fue definido.

Ejemplo

1{
2 "id": "EVT-abc123",
3 "event_type": "product.access_granted",
4 "created_at": "2024-11-14T11:00:00Z",
5 "data": {
6 "id": "PA-abc123",
7 "progress": 0,
8 "accessible": true,
9 "access_due_date": null,
10 "created_at": "2024-11-14T11:00:00Z",
11 "product": {
12 "id": "PROD-abc123",
13 "slug": "ruby-avanzado",
14 "name": "Ruby Avanzado"
15 },
16 "user": {
17 "id": "USR-abc123",
18 "name": "Jane",
19 "last_name": "Doe",
20 "email": "jane@example.com",
21 "external_id": null
22 }
23 }
24}

payment.succeeded

Se dispara cuando un pago se procesa exitosamente. Puede corresponder a una compra única, al cobro de una cuota o al pago de una suscripción.

CampoTipoDescripción
idstringID del pago.
statusstringEstado del pago. Posibles valores: succeeded, failed, refunded, charged_back, canceled, pending, expired
reasonstringMotivo del pago, posibles valores: one_time_payment, first_installment, installment o subscription.
payment_gatewaystringGateway utilizado, valores posiblesstripe, mercadopago, wisboo_pay, payu, paypal, offline (el pago se realiza fuera de la plataforma).
payment_methodstringMétodo de pago (ej. visa, pix).
payment_method_typestringTipo de método más específico (ej. card, bank_transfer).
external_idstringID de la transacción en el gateway de pago.
paid_amountnumberMonto total cobrado.
net_amountnumberMonto después de comisiones.
fee_amountnumberComisión total de la plataforma.
processor_feenumberComisión cobrada por el procesador de pagos.
taxes_amountnumberMonto de impuestos en moneda local.
currencystringMoneda ISO 4217 de la transacción.
settlement_currencystringMoneda ISO 4217 en la que se liquidará el pago. Solo relevante para transacciones de WisbooPay.
parent_transaction_idstringID de la transacción original que dio pie a este pago, puede ser una compra o una suscripción.
paid_atstringTimestamp ISO 8601 de confirmación del pago.
created_atstringTimestamp ISO 8601 de cuándo se creó el registro de pago.

Objeto product

CampoTipoDescripción
idstringID del producto.
slugstringSlug de la URL.
namestringNombre del producto.

Objeto user

CampoTipoDescripción
idstringID del usuario.
namestringNombre.
last_namestringApellido.
emailstringDirección de email.
external_idstringTu identificador para este usuario. null si no fue definido.

Ejemplo

1{
2 "id": "EVT-abc123",
3 "event_type": "payment.succeeded",
4 "created_at": "2024-11-14T12:00:00Z",
5 "data": {
6 "id": "PAY-abc123",
7 "status": "succeeded",
8 "reason": "one_time_payment",
9 "payment_gateway": "stripe",
10 "payment_method": "card",
11 "payment_method_type": "visa",
12 "external_id": "pi_l2kf0932nf0003400",
13 "paid_amount": 99.00,
14 "net_amount": 93.15,
15 "fee_amount": 5.85,
16 "processor_fee": 3.19,
17 "taxes_amount": 0.00,
18 "currency": "USD",
19 "settlement_currency": "USD",
20 "parent_transaction_id": "RXPXX0P3",
21 "paid_at": "2024-11-14T12:00:05Z",
22 "created_at": "2024-11-14T12:00:00Z",
23 "product": {
24 "id": "PROD-abc123",
25 "name": "Ruby Avanzado",
26 "slug": "ruby-avanzado"
27 },
28 "user": {
29 "id": "USR-abc123",
30 "name": "Jane",
31 "last_name": "Doe",
32 "email": "jane@example.com",
33 "external_id": null
34 }
35 }
36}

course.completed

Se dispara cuando un usuario completa un curso.

CampoTipoDescripción
idstringID de la inscripción.
gradenumberCalificación final de 0.0 a 100.0.
passedbooleanSi el usuario alcanzó el umbral de aprobación.
started_atstringFecha ISO 8601 en la cual se comenzó el curso (Primer contenido visto/completado).
completed_atstringFecha ISO 8601 en que se completó el curso. null si aún no fue marcado como completo.
concept_gradenumberNota asignada manualmente al estudiante
created_atstringTimestamp ISO 8601 de cuándo se creó la inscripción.

Objeto product

CampoTipoDescripción
idstringID del producto.
slugstringSlug de la URL.
namestringNombre del producto.

Objeto user

CampoTipoDescripción
idstringID del usuario.
namestringNombre.
last_namestringApellido.
emailstringDirección de email.
external_idstringTu identificador para este usuario. null si no fue definido.

Ejemplo

1{
2 "id": "EVT-abc123",
3 "event_type": "course.completed",
4 "created_at": "2024-11-14T15:00:00Z",
5 "data": {
6 "id": "PA-abc123",
7 "grade": 87.5,
8 "passed": true,
9 "completed_at": "2024-11-14T14:55:00Z",
10 "started_at": "2024-10-02T13:00:00Z",
11 "created_at": "2024-10-01T09:00:00Z",
12 "product": {
13 "id": "PROD-abc123",
14 "slug": "ruby-avanzado",
15 "name": "Ruby Avanzado"
16 },
17 "user": {
18 "id": "USR-abc123",
19 "name": "Jane",
20 "last_name": "Doe",
21 "email": "jane@example.com",
22 "external_id": null
23 }
24 }
25}