> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.wisboo.com/webhooks/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.wisboo.com/_mcp/server. # Webhooks 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. reintento | Tiempo | | -------------- | ----------- | | 1 | 10 segundos | | 2 | 1 minuto | | 3 | 10 minutos | | 4 | 1 hora | | 5 | 3 horas | --- ## Formato de la solicitud Cada solicitud de webhook es un `POST` con `Content-Type: application/json`. El cuerpo siempre tiene la siguiente estructura: ```json { "id": "EVT-abc123", "event_type": "product.sold", "created_at": "2024-11-14T10:00:00Z", "data": { ... } } ``` | Campo | Tipo | Descripción | | --------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `string` | ID único del evento, con prefijo `EVT-`. | | `event_type` | `string` | El evento que disparó la entrega (ver [Eventos soportados](#eventos-soportados)). | | `created_at` | `string` | Timestamp ISO 8601 de cuándo se creó el evento. | | `data` | `object` | Payload específico del evento (ver la sección de cada evento más abajo). | | `previous_attributes` | `object` | **Solo presente en eventos `*.updated`.** Mapa de los campos que cambiaron con sus valores **anteriores** al cambio. Los campos que no cambiaron no se incluyen. | --- ## 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) ```ruby def valid_signature?(header, raw_body, secret, tolerance: 300) parts = header.split(',').map { |p| p.split('=', 2) }.to_h timestamp = parts['t'].to_i signature = parts['v1'] return false if (Time.current.to_i - timestamp).abs > tolerance expected = OpenSSL::HMAC.hexdigest('SHA256', secret, "#{timestamp}.#{raw_body}") ActiveSupport::SecurityUtils.secure_compare(expected, signature) end ``` ### Ejemplo (Node.js) ```js const crypto = require('crypto'); function isValidSignature(header, rawBody, secret, toleranceSecs = 300) { const parts = Object.fromEntries(header.split(',').map(p => p.split('='))); const timestamp = parseInt(parts.t, 10); const signature = parts.v1; if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSecs) return false; const expected = crypto .createHmac('sha256', secret) .update(`${timestamp}.${rawBody}`) .digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); } ``` > **Importante:** siempre calcular el HMAC sobre los bytes **crudos** del cuerpo de la solicitud, antes de cualquier parseo de JSON. --- ## Eventos soportados | Evento | Descripción | | -------------------------------------------------- | -------------------------------------------- | | [`product.created`](#productcreated) | Se creó un nuevo producto. | | [`product.deleted`](#productdeleted) | Se eliminó un producto. | | [`product.sold`](#productsold) | Se realizó la compra de un producto. | | [`product.access_granted`](#productaccess_granted) | Se otorgó acceso a un producto a un usuario. | | [`payment.succeeded`](#paymentsucceeded) | Se procesó exitosamente un pago. | | [`course.completed`](#coursecompleted) | Un usuario completó un curso. | | [`user.created`](#usercreated) | Se registró un nuevo usuario. | | [`user.updated`](#userupdated) | Se actualizaron datos de un usuario. | --- ## Payloads por evento ### `product.created` Se dispara cuando se crea un nuevo producto. | Campo | Tipo | Descripción | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `id` | `string` | ID del producto. | | `name` | `string` | Nombre del producto. | | `product_type` | `string` | Tipo de producto. Posibles valores: `course_type`, `bundle_type`, `subscription_type`, `webinar_type`, `downloadable_type`. | | `state` | `string` | Estado de publicación. Posibles valores: `not_published`, `published`, `archived`. | | `access_type` | `string` | Tipo de acceso. Posibles valores: `free_access`, `paid_access`. | | `slug` | `string` | Slug de la URL. | | `created_at` | `string` | Timestamp ISO 8601 de creación. | **Ejemplo** ```json { "id": "EVT-abc123", "event_type": "product.created", "created_at": "2024-11-14T10:00:00Z", "data": { "id": "PRO-abc123", "name": "Ruby Avanzado", "product_type": "course_type", "state": "not_published", "access_type": "paid_access", "slug": "ruby-avanzado", "created_at": "2024-11-14T10:00:00Z" } } ``` --- ### `product.deleted` Se dispara cuando un producto es eliminado. | Campo | Tipo | Descripción | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `id` | `string` | ID del producto. | | `name` | `string` | Nombre del producto. | | `product_type` | `string` | Tipo de producto. Posibles valores: `course_type`, `bundle_type`, `subscription_type`, `webinar_type`, `downloadable_type`. | | `state` | `string` | Estado en el momento de la eliminación. Siempre `not_published`. | | `access_type` | `string` | Tipo de acceso. Posibles valores: `free_access`, `paid_access`. | | `slug` | `string` | Slug de la URL. | | `created_at` | `string` | Timestamp ISO 8601 de creación original del producto. | **Ejemplo** ```json { "id": "EVT-abc123", "event_type": "product.deleted", "created_at": "2024-11-20T09:00:00Z", "data": { "id": "PRO-abc123", "name": "Ruby Avanzado", "product_type": "course_type", "state": "not_published", "access_type": "paid_access", "slug": "ruby-avanzado", "created_at": "2024-11-14T10:00:00Z" } } ``` --- ### `user.created` Se dispara cuando un nuevo usuario se registra. | Campo | Tipo | Descripción | | --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `string` | ID del usuario. | | `name` | `string` | Nombre. | | `last_name` | `string` | Apellido. | | `email` | `string` | Dirección de email. | | `country` | `string` | Código de país ISO 3166-1 alpha-2. `null` si no fue provisto. | | `phone` | `string` | Número de teléfono. `null` si no fue provisto. | | `external_id` | `string` | Tu propio identificador para este usuario, si fue definido. `null` en caso contrario. | | `created_at` | `string` | Timestamp ISO 8601. | | `custom_fields` | `object` | Mapa clave-valor de campos adicionales recolectados en el registro. `phone` y `country` se excluyen de este mapa (aparecen como campos de primer nivel). | **Ejemplo** ```json { "id": "EVT-abc123", "event_type": "user.created", "created_at": "2024-11-14T10:00:00Z", "data": { "id": "USR-abc123", "name": "Jane", "last_name": "Doe", "email": "jane@example.com", "country": "AR", "phone": "+5491123456789", "external_id": null, "created_at": "2024-11-14T10:00:00Z", "custom_fields": { "company": "Acme" } } } ``` --- ### `product.sold` Se dispara cuando se completa la compra de un producto. #### Campos principales | Campo | Tipo | Descripción | | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `string` | ID de la compra. | | `status` | `string` | Estado 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_id` | `string` | ID del último pago asociado. `null` si no hay ninguno. | | `payment_gateway` | `string` | Gateway utilizado, valores posibles`stripe`, `mercadopago`, `wisboo_pay`, `payu`, `paypal`, `offline` (el pago se realiza fuera de la plataforma), `none` (se aplicó un cupón del 100%). | | `base_price` | `string` | Precio de lista original. | | `promotional_price` | `string` | Precio tras la promoción, antes de otros ajustes. | | `promotion_percentage_off` | `string` | Descuento de promoción como porcentaje (0–100). | | `promotion_amount_off` | `string` | Descuento de promoción como monto fijo. | | `promotion_type` | `string` | Tipo de promoción aplicada. `null` si no aplica. Posible valores: `hotsale`, `popup`, `gift`, `two_for_one` | | `discounted_amount` | `string` | Monto total descontado. | | `extra_charge` | `string` | Cargo adicional aplicado. | | `extra_charge_percentage` | `string` | Cargo adicional como porcentaje. | | `extra_charge_name` | `string` | Etiqueta del cargo adicional. `null` si no aplica. | | `taxes_amount` | `string` | Monto de impuestos. | | `total_amount` | `string` | Monto final cobrado al comprador. | | `currency` | `string` | Código de moneda ISO 4217 de la transacción. | | `installments` | `integer` | Cantidad de cuotas. `1` para pago único. | | `installment_plan_interval` | `string` | Intervalo entre cuotas. `null` para pagos únicos. Posible valores: `daily`, `weekly`, `biweekly`, `monthly`, `yearly`. Solo aplicable para transacciones de Wisboo Pay. | | `installment_amount` | `string` | Monto de la cuota. Solo aplicable para transacciones de Wisboo Pay. | | `installment_interest_rate` | `string` | Tasa de interés aplicada por cuota. Solo aplicable para transacciones de Wisboo Pay. | | `installment_interest_rate_amount` | `string` | Monto de interés por cuota. Solo aplicable para transacciones de Wisboo Pay. | | `installment_plan_end_date` | `string` | Fecha ISO 8601 en que finaliza el plan de cuotas. `null` para pagos únicos. Solo aplicable para transacciones de Wisboo Pay. | | `charged_installments` | `number` | Cantidad de cuotas pagadas hasta la fecha. `null` para pagos únicos. Solo | | aplicable para transacciones de Wisboo Pay. | | | | `amount_due` | `string` | Monto pendiente por pagar. Será 0 para compras pagadas en su totalidad. | | `amount_paid` | `string` | Monto pagado hasta la fecha del total de la transacción. | | `next_charge_at` | `string` | Timestamp 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_id` | `string` | ID del usuario recipiente de una compra hecha por promoción de regalo (`gift`) o 2x1 (`two_for_one`). | | `gift_recipient_email` | `string` | Email del usuario recipiente de una compra hecha por promoción de regalo (`gift`) o 2x1 (`two_for_one`). | | `custom_fields` `object` | Mapa 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_at` | `string` | Timestamp ISO 8601 de la compra. | | `paid_at` | `string` | Timestamp 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. | Campo | Tipo | Descripción | | ------------ | -------- | -------------------------- | | `percentage` | `string` | Porcentaje de descuento. | | `code` | `string` | El código promocional. | | `id` | `string` | ID del código promocional. | #### Objeto `product` | Campo | Tipo | Descripción | | ------ | -------- | --------------------------------------------------- | | `id` | `string` | ID del producto. | | `name` | `string` | Nombre del producto. | | `type` | `string` | Tipo de producto (ej. `course`, `digital_product`). | | `slug` | `string` | Slug de la URL. | #### Objeto `user` | Campo | Tipo | Descripción | | ------------- | -------- | -------------------------------------------------------------- | | `id` | `string` | ID del comprador. | | `name` | `string` | Nombre. | | `last_name` | `string` | Apellido. | | `email` | `string` | Dirección de email. | | `external_id` | `string` | Tu identificador para este usuario. `null` si no fue definido. | #### Objeto `checkout_session` | Campo | Tipo | Descripción | | -------------- | -------- | ---------------------------------------------- | | `id` | `string` | ID de la sesión de checkout. | | `started_at` | `string` | Timestamp ISO 8601 de inicio de la sesión. | | `ip` | `string` | Dirección IP del comprador. | | `device_type` | `string` | Tipo de dispositivo (ej. `desktop`, `mobile`). | | `user_agent` | `string` | User agent del navegador. | | `utm_source` | `string` | UTM source. `null` si no está presente. | | `utm_medium` | `string` | UTM medium. `null` si no está presente. | | `utm_campaign` | `string` | UTM campaign. `null` si no está presente. | | `utm_term` | `string` | UTM term. `null` si no está presente. | | `utm_content` | `string` | UTM content. `null` si no está presente. | **Ejemplo** ```json { "id": "EVT-abc123", "event_type": "product.sold", "created_at": "2024-11-14T10:00:00Z", "data": { "id": "RXPXX0P3", "status": "paid", "payment_id": "PAY-NCOA076X1EK84YCX", "payment_gateway": "mercadopago", "base_price": "10000", "promotion_percentage_off": "0", "promotion_amount_off": "0", "promotional_price": "10000", "discounted_amount": "0", "extra_charge": "0", "extra_charge_percentage": "0", "extra_charge_name": null, "taxes_amount": "0", "installment_plan_interval": null, "installments": 1, "installment_amount": "0", "installment_interest_rate": "0", "installment_interest_rate_amount": "0", "amount_due": "0", "amount_paid": "10000", "charged_installments": 1, "installment_plan_end_date": null, "next_charge_at": null, "total_amount": "10000", "currency": "ARS", "gift_recipient_id": null, "gift_recipient_email": null, "custom_fields":{ "extra_request": "Extra services" }, "promotion_type": null, "checkout_session": { "id": "CHS-97SBU3N4VAZRMOPU", "started_at": "2026-08-07T14:15:06Z", "ip": "192.230.91.1", "device_type": "desktop", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, li ke Gecko) Chrome/149.0.0.0 Safari/537.36", "utm_medium": null, "utm_source": null, "utm_campaign": null, "utm_term": null, "utm_content": null }, "discount": null, "product": { "id": "PRO-019fdc57046578ce86071d30fc541", "name": "Curso", "type": "course_type", "slug": "curso" }, "user": { "id":"USR-06a75e87a2bf7000b497bbd3797955aa", "name": "Jane", "last_name": "Doe", "email": "jane@example.com", "external_id": null, } } } ``` --- ### `product.access_granted` Se dispara cuando un usuario obtiene acceso a un producto, independientemente del motivo (compra, asignación manual, inscripción gratuita, etc.). | Campo | Tipo | Descripción | | ----------------- | --------- | -------------------------------------------------------------------- | | `id` | `string` | ID del registro de acceso. | | `progress` | `number` | Progreso de completitud de `0` a `100`. | | `accessible` | `boolean` | Si el usuario tiene acceso activo actualmente. | | `access_due_date` | `string` | Datetime ISO 8601 de vencimiento del acceso. `null` si es ilimitado. | | `created_at` | `string` | Timestamp ISO 8601 de cuándo se otorgó el acceso. | #### Objeto `product` | Campo | Tipo | Descripción | | ------ | -------- | -------------------- | | `id` | `string` | ID del producto. | | `slug` | `string` | Slug de la URL. | | `name` | `string` | Nombre del producto. | #### Objeto `user` | Campo | Tipo | Descripción | | ------------- | -------- | -------------------------------------------------------------- | | `id` | `string` | ID del usuario. | | `name` | `string` | Nombre. | | `last_name` | `string` | Apellido. | | `email` | `string` | Dirección de email. | | `external_id` | `string` | Tu identificador para este usuario. `null` si no fue definido. | **Ejemplo** ```json { "id": "EVT-abc123", "event_type": "product.access_granted", "created_at": "2024-11-14T11:00:00Z", "data": { "id": "PA-abc123", "progress": 0, "accessible": true, "access_due_date": null, "created_at": "2024-11-14T11:00:00Z", "product": { "id": "PROD-abc123", "slug": "ruby-avanzado", "name": "Ruby Avanzado" }, "user": { "id": "USR-abc123", "name": "Jane", "last_name": "Doe", "email": "jane@example.com", "external_id": null } } } ``` --- ### `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. | Campo | Tipo | Descripción | | ----------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | `string` | ID del pago. | | `status` | `string` | Estado del pago. Posibles valores: `succeeded`, `failed`, `refunded`, `charged_back`, `canceled`, `pending`, `expired` | | `reason` | `string` | Motivo del pago, posibles valores: `one_time_payment`, `first_installment`, `installment` o `subscription`. | | `payment_gateway` | `string` | Gateway utilizado, valores posibles`stripe`, `mercadopago`, `wisboo_pay`, `payu`, `paypal`, `offline` (el pago se realiza fuera de la plataforma). | | `payment_method` | `string` | Método de pago (ej. `visa`, `pix`). | | `payment_method_type` | `string` | Tipo de método más específico (ej. `card`, `bank_transfer`). | | `external_id` | `string` | ID de la transacción en el gateway de pago. | | `paid_amount` | `string` | Monto total cobrado. | | `net_amount` | `string` | Monto después de comisiones. | | `fee_amount` | `string` | Comisión total de la plataforma. | | `processor_fee` | `string` | Comisión cobrada por el procesador de pagos. | | `taxes_amount` | `string` | Monto de impuestos en moneda local. | | `currency` | `string` | Moneda ISO 4217 de la transacción. | | `settlement_currency` | `string` | Moneda ISO 4217 en la que se liquidará el pago. Solo relevante para transacciones de WisbooPay. | | `parent_transaction_id` | `string` | ID de la transacción original que dio pie a este pago, puede ser una compra o una suscripción. | | `paid_at` | `string` | Timestamp ISO 8601 de confirmación del pago. | | `created_at` | `string` | Timestamp ISO 8601 de cuándo se creó el registro de pago. | #### Objeto `product` | Campo | Tipo | Descripción | | ------ | -------- | -------------------- | | `id` | `string` | ID del producto. | | `slug` | `string` | Slug de la URL. | | `name` | `string` | Nombre del producto. | #### Objeto `user` | Campo | Tipo | Descripción | | ------------- | -------- | -------------------------------------------------------------- | | `id` | `string` | ID del usuario. | | `name` | `string` | Nombre. | | `last_name` | `string` | Apellido. | | `email` | `string` | Dirección de email. | | `external_id` | `string` | Tu identificador para este usuario. `null` si no fue definido. | **Ejemplo** ```json { "id": "EVT-abc123", "event_type": "payment.succeeded", "created_at": "2024-11-14T12:00:00Z", "data": { "id": "PAY-abc123", "status": "succeeded", "reason": "one_time_payment", "payment_gateway": "stripe", "payment_method": "card", "payment_method_type": "visa", "external_id": "pi_l2kf0932nf0003400", "paid_amount": "99.00", "net_amount": "93.15", "fee_amount": "5.85", "processor_fee": "3.19", "taxes_amount": "0", "currency": "USD", "settlement_currency": "USD", "parent_transaction_id": "RXPXX0P3", "paid_at": "2024-11-14T12:00:05Z", "created_at": "2024-11-14T12:00:00Z", "product": { "id": "PROD-abc123", "name": "Ruby Avanzado", "slug": "ruby-avanzado" }, "user": { "id": "USR-abc123", "name": "Jane", "last_name": "Doe", "email": "jane@example.com", "external_id": null } } } ``` --- ### `course.completed` Se dispara cuando un usuario completa un curso. | Campo | Tipo | Descripción | | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- | | `id` | `string` | ID de la inscripción. | | `grade` | `string` | Calificación final del estudiante dentro del curso, la escala dependerá de lo que haya sido configurado en la administración. | | `passed` | `boolean` | Si el usuario alcanzó el umbral de aprobación. | | `started_at` | `string` | Fecha ISO 8601 en la cual se comenzó el curso (Primer contenido visto/completado). | | `completed_at` | `string` | Fecha ISO 8601 en que se completó el curso. `null` si aún no fue marcado como completo. | | `concept_grade` | `string` | Nota asignada manualmente al estudiante | | `created_at` | `string` | Timestamp ISO 8601 de cuándo se creó la inscripción. | #### Objeto `product` | Campo | Tipo | Descripción | | ------ | -------- | -------------------- | | `id` | `string` | ID del producto. | | `slug` | `string` | Slug de la URL. | | `name` | `string` | Nombre del producto. | #### Objeto `user` | Campo | Tipo | Descripción | | ------------- | -------- | -------------------------------------------------------------- | | `id` | `string` | ID del usuario. | | `name` | `string` | Nombre. | | `last_name` | `string` | Apellido. | | `email` | `string` | Dirección de email. | | `external_id` | `string` | Tu identificador para este usuario. `null` si no fue definido. | **Ejemplo** ```json { "id": "EVT-abc123", "event_type": "course.completed", "created_at": "2024-11-14T15:00:00Z", "data": { "id": "PA-abc123", "grade": "87.50", "passed": true, "completed_at": "2024-11-14T14:55:00Z", "started_at": "2024-10-02T13:00:00Z", "created_at": "2024-10-01T09:00:00Z", "concept_grade": null, "product": { "id": "PROD-abc123", "slug": "ruby-avanzado", "name": "Ruby Avanzado" }, "user": { "id": "USR-abc123", "name": "Jane", "last_name": "Doe", "email": "jane@example.com", "external_id": null } } } ``` --- ### `user.updated` Se dispara cuando se actualiza alguno de los siguientes campos de un usuario: `name`, `last_name`, `email`, `language`, `avatar` o cualquier campo personalizado (`custom_fields`). El payload tiene la misma estructura `data` que [`user.created`](#usercreated), más un campo `previous_attributes` en el nivel raíz con los valores **anteriores** de los campos que cambiaron. | Campo raíz | Tipo | Descripción | | --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `data` | `object` | Estado actual del usuario (mismos campos que `user.created`). | | `previous_attributes` | `object` | Valores anteriores, únicamente de los campos que cambiaron. Puede incluir cualquier combinación de `name`, `last_name`, `email`, `language`, `avatar` y `custom_fields`. | **Ejemplo — cambio de nombre y email** ```json { "id": "EVT-abc123", "event_type": "user.updated", "created_at": "2024-11-15T08:30:00Z", "data": { "id": "USR-abc123", "name": "Jane", "last_name": "Doe", "email": "jane.new@example.com", "country": "AR", "phone": "+5491123456789", "external_id": null, "created_at": "2024-11-14T10:00:00Z", "custom_fields": { "company": "Acme" } }, "previous_attributes": { "name": "Jana", "email": "jana.old@example.com" } } ``` **Ejemplo — cambio de campo personalizado** ```json { "id": "EVT-def456", "event_type": "user.updated", "created_at": "2024-11-16T11:00:00Z", "data": { "id": "USR-abc123", "name": "Jane", "last_name": "Doe", "email": "jane.new@example.com", "country": "AR", "phone": "+5491123456789", "external_id": null, "created_at": "2024-11-14T10:00:00Z", "custom_fields": { "company": "New Corp" } }, "previous_attributes": { "custom_fields": { "company": "Acme" } } } ```