¿Qué es un Webhook?

Webhook es un mecanismo que permite a tu sistema recibir notificaciones automáticas sobre eventos mediante solicitudes HTTP. Cuando ocurre un determinado evento en nuestro sistema (por ejemplo, una campaña se completa), enviamos una solicitud POST con información detallada a la URL que especifiques.

Configuración de Webhook

Puedes configurar webhooks en el panel de control web:

  1. Ve a la sección Integraciones.
  2. Haz clic en la tarjeta Webhooks.
  3. Haz clic en Crear Webhook.
  4. Completa los campos:
    • Nombre del webhook — para tu identificación personal del webhook.
    • URL para enviar el webhook — la dirección de tu servidor que recibirá las notificaciones.
    • Eventos — selecciona de la lista los eventos sobre los que deseas recibir notificaciones.
  5. Haz clic en Crear Webhook.

Después de la creación, el webhook aparecerá en la lista general. Su tarjeta muestra toda la información clave:

  • Estado (Activo / Inactivo)
  • Nombre del webhook y URL para enviar el webhook
  • Lista de eventos rastreados
  • Hora del último envío exitoso
  • Actividad (número total de eventos enviados)

Las siguientes acciones están disponibles en el menú (⋮) en la esquina de la tarjeta:

  • Probar
  • Editar
  • Activar / Desactivar
  • Eliminar

Formato de Notificaciones

{
  "eventId": "<uuid>",
  "trigger": "<entity>.<event>",
  "occurredAt": "2023-10-27T12:35:01.123Z",
  "payload": {
    "someStringField": "string",
    "numberField": 42
  }
}

Campos de la solicitud:

  • eventId — identificador único del evento.
  • trigger — tipo de evento en formato entidad.evento (ver lista a continuación).
  • occurredAt — hora en que ocurrió el evento en formato ISO 8601 (UTC).
  • payload — objeto con datos específicos para cada evento.

Evento de Prueba

Cuando hagas clic en el botón Probar en el menú (⋮) en la esquina de la tarjeta, recibirás una solicitud POST con el siguiente payload:

{
  "eventId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "trigger": "emailmassivo.webhook_test",
  "occurredAt": "2030-12-31T20:59:59.999Z",
  "payload": {
    "timestamp": "2030-12-31T20:59:59.999Z",
    "stringField": "someString",
    "numberField": 42,
    "booleanField": true,
    "objectField": {
      "nestedString": "value",
      "nestedNumber": 123
    },
    "arrayField": ["item1", "item2", 3, false]
  }
}

Disparadores Disponibles

Entidad: Campañas de email (mail_distribution)

Payload Común para eventos de campaña

Para los eventos mail_distribution.started, mail_distribution.completed, mail_distribution.moderating, mail_distribution.approved y mail_distribution.rejected, se utiliza la siguiente estructura de payload:

{
  "mailDistributionId": 12345,
  "mailDistributionName": "Nombre de la campaña",
  "mailDistributionSubject": "Asunto del email"
}
  • mail_distribution.started — la campaña ha sido iniciada (Iniciada).
  • mail_distribution.completed — la campaña se completó exitosamente (Completada).
  • mail_distribution.moderating — la campaña fue enviada para revisión (En moderación).
  • mail_distribution.approved — la campaña fue aprobada por el moderador (Aprobada).
  • mail_distribution.rejected — la campaña fue rechazada por el moderador (Rechazada).
  • mail_distribution.banned — la campaña está bloqueada (Detenida por el sistema). Este evento tiene un payload extendido.

Payload:

{
  "mailDistributionId": 12345,
  "mailDistributionName": "Nombre de la campaña",
  "mailDistributionSubject": "Asunto del email",
  "negative": {
    "reason": "hard_bounced",
    "explanation": "Descripción detallada de la razón del bloqueo"
  }
}

El campo reason contendrá una de las siguientes cadenas:

  • hard_bounced
  • soft_bounced
  • error_spam
  • complain

Requisitos de Procesamiento

1. Respuesta del servidor y tiempo de espera

Tu servidor debe responder a la solicitud dentro de 30 segundos.

  • Estado HTTP 200299: significa que el evento fue recibido y procesado exitosamente.
  • Cualquier otro estado (3xx, 4xx, 5xx) o tiempo de espera agotado: se considera un error, después del cual intentaremos entregar el evento nuevamente.

2. Idempotencia

Nuestro sistema garantiza la entrega con el principio de al menos una vez (at-least-once). Esto significa que debido a problemas de red o errores en el lado de tu servidor, el mismo evento puede ser entregado más de una vez. Usa el campo eventId para prevenir el procesamiento duplicado.

3. Reintentos

En caso de fallo en la entrega, realizaremos 6 intentos de reintento con intervalos crecientes.

Intento Retraso después del anterior
1 1 minuto
2 2 minutos
3 4 minutos
4 8 minutos
5 15 minutos
6 15 minutos

En total podemos enviar hasta 7 solicitudes para un evento (envío inicial + 6 reintentos). Después del último intento fallido, dejaremos de entregar el evento y será marcado como no entregado.

4. Requisitos de URL

Tu manejador de URL debe usar el protocolo HTTPS y tener un certificado SSL válido. Las notificaciones a direcciones HTTP no serán entregadas.

Ejemplo de Manejador

PHP

<?php
// Obtenemos el cuerpo raw de la solicitud
$input = file_get_contents('php://input');
$data = json_decode($input, true);

if (!$data || !isset($data['eventId'])) {
    http_response_code(400); // Bad Request
    exit('Invalid payload');
}

// Verificamos que el evento no fue procesado anteriormente
if (isEventProcessed($data['eventId'])) {
    http_response_code(200);
    exit('OK. Already processed.');
}

// Procesamos el evento
switch ($data['trigger']) {
    case 'mail_distribution.completed':
        handleMailDistributionCompleted($data['payload']);
        break;
    case 'mail_distribution.banned':
        handleMailDistributionBanned($data['payload']);
        break;
    // ... otros eventos
}

// Marcamos el evento como procesado
markEventAsProcessed($data['eventId']);

http_response_code(200);
echo 'OK';
?>

Node.js (Express)

const express = require('express');
const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  const { eventId, trigger, payload } = req.body;

  // Verificamos la idempotencia
  if (isEventProcessed(eventId)) {
    return res.status(200).send('OK. Already processed.');
  }

  // Procesamos el evento
  switch (trigger) {
    case 'mail_distribution.completed':
      handleMailDistributionCompleted(payload);
      break;
    case 'mail_distribution.banned':
      handleMailDistributionBanned(payload);
      break;
    // ... otros eventos
  }

  // Marcamos como procesado
  markEventAsProcessed(eventId);

  res.status(200).send('OK');
});

Recomendaciones de Desarrollo

1. Procesa las solicitudes de forma asíncrona

Tu servidor debe responder con HTTP 200 OK lo más rápido posible. No realices operaciones de larga duración (llamadas a otras API, procesamiento de archivos) en el momento de recibir el webhook. En su lugar, agrega la tarea a una cola (por ejemplo, RabbitMQ, Redis, SQS) y devuelve la respuesta inmediatamente. Esto te protegerá de tiempos de espera agotados y facilitará el escalado del procesamiento.

2. Prepárate para nuevos campos

Se pueden agregar nuevos campos al objeto payload en el futuro. Tu código debe ser resistente a esto y no fallar si aparecen claves desconocidas en el JSON.

3. Configura el monitoreo

Configura el monitoreo para tu manejador de URL. Las alertas te ayudarán a reaccionar a los problemas rápidamente y corregirlos antes de que los eventos comiencen a perderse después de todos los intentos de reintento.

4. Usa entornos de prueba

No uses tu URL principal (producción) para depuración. Crea un webhook separado para un entorno de prueba o staging para realizar cambios en el código de forma segura.

Preguntas Frecuentes (FAQ)

  • He configurado un webhook pero no estoy recibiendo notificaciones. ¿Qué debo hacer?

    Verifica lo siguiente:

    1. Estado en la UI: Asegúrate de que el webhook esté Activo.
    2. Evento de prueba: Usa el botón Probar en la tarjeta del webhook.
    3. URL pública: Asegúrate de que la URL sea accesible desde internet (no localhost).
    4. HTTPS: Asegúrate de que la URL comience con https:// y tenga un certificado SSL válido.
    5. Logs del servidor: Revisa los logs de tu servidor web (Nginx, Apache) o de la aplicación para ver entradas de solicitudes entrantes.
  • ¿Por qué envían el mismo evento (eventId) varias veces?

    Repetimos el envío si no recibimos confirmación de entrega exitosa de tu servidor (respuesta con código HTTP 200–299) dentro de 30 segundos. Esto puede suceder por dos razones principales:

    1. Tu servidor devolvió un error (por ejemplo, estado 500 Internal Server Error).
    2. Ocurrió un problema de red, debido al cual no recibimos la respuesta, incluso si tu aplicación la envió y procesó el evento exitosamente.

    Por lo tanto, tu sistema siempre debe estar preparado para duplicados (ver la sección Idempotencia).

  • ¿Está garantizado el orden de entrega de eventos?

    No, no lo está. Debido al comportamiento de la red o la lógica de reintentos, un evento que ocurrió más tarde puede ser entregado antes. Siempre confía en la marca de tiempo occurredAt en el cuerpo de la solicitud, no en el orden en que se reciben.

  • ¿Qué sucede si mi servidor no está disponible durante mucho tiempo?

    Intentaremos entregar el evento durante aproximadamente 45 minutos. Si tu servidor permanece no disponible por más tiempo, el evento se perderá. Planifica el mantenimiento teniendo en cuenta esta ventana.

  • ¿Puedo usar la misma URL para múltiples webhooks?

    Sí. Esta es una práctica común. En tu código usa el campo trigger para el enrutamiento y la llamada a la lógica de procesamiento apropiada.