Saltar al contenido principal

Documentación de Notificaciones Webhook

¿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

Entidad: Correos transaccionales (email)​

Eventos sobre los correos transaccionales enviados por la API y por SMTP. En el formulario del webhook es el grupo «Correos transaccionales — detallados». A diferencia de los eventos anteriores de correos transaccionales, incluyen el ID del correo, el tipo de destinatario y el motivo del rebote: recomendamos este grupo para nuevas integraciones.

Cada evento se refiere a un destinatario del correo: si el correo se envió a tres direcciones, recibirás eventos de cada una.

Payload común​
{
"emailId": "0199b2f4-6c1e-7a3d-9f2b-4e8c1d7a5b60",
"recipient": {
"id": "c4a07e13-5d2b-4e8f-b916-7f3a2d0c8b44",
"email": "user@example.com",
"type": "to"
}
}
  • emailId — el ID del correo que devolvió la API al enviarlo (data.id).
  • recipient.id — el ID del destinatario dentro del correo.
  • recipient.email — la dirección del destinatario.
  • recipient.type — to es el destinatario principal, cc una copia, bcc una copia oculta.
Eventos​
  • email.delivered — «Entregado»: el servidor de correo del destinatario aceptó el correo.
  • email.opened — «Apertura»: el destinatario abrió el correo por primera vez.
  • email.clicked — «Clic»: el destinatario hizo clic por primera vez en un enlace del correo. El payload incluye también url, el enlace que siguió.
  • email.unsubscribed — «Desuscripción»: el destinatario se dio de baja de los correos de esta clave de envío.
  • email.complained — «Queja»: el destinatario marcó el correo como spam.
  • email.bounced — «Rebotado»: el correo no se entregó: la dirección no existe, el buzón está lleno, el servidor del destinatario lo rechazó o la dirección ya se conoce como no entregable.
  • email.failed — «Error de envío»: no se pudo enviar el correo. La dirección del destinatario está bien: puedes volver a enviar el correo.
  • email.suppressed — «No enviado»: el destinatario se había dado de baja de los correos de esta clave de envío o se había quejado de ellos, por lo que el correo no se envió.
Motivo del rebote​

Los eventos email.bounced, email.failed y email.suppressed tienen un objeto reason en el payload:

{
"emailId": "0199b2f4-6c1e-7a3d-9f2b-4e8c1d7a5b60",
"recipient": {
"id": "c4a07e13-5d2b-4e8f-b916-7f3a2d0c8b44",
"email": "user@example.com",
"type": "to"
},
"reason": {
"code": "DELIVERY_MAILBOX_FULL",
"bounceType": "transient",
"message": "Recipient mailbox is full.",
"smtpResponse": "552 5.2.2 Mailbox full"
}
}
  • code — el código del motivo, por ejemplo DELIVERY_MAILBOX_FULL (buzón lleno) o RECIPIENT_UNSUBSCRIBED (el destinatario se dio de baja).
  • bounceType — solo en email.bounced: permanent — la dirección no existe, elimínala de tu lista; transient — rechazo temporal, la dirección se puede seguir usando; undetermined — el motivo no está claro.
  • message — descripción del motivo (en inglés).
  • smtpResponse — la respuesta del servidor de correo del destinatario, si existe.

¡Importante! Elimina una dirección de tu lista solo después de email.bounced con bounceType: permanent.

A veces un servidor de correo informa de la no entrega después de haber aceptado el correo. Entonces, para el mismo destinatario, email.bounced llega después de email.delivered.

Todos los códigos de motivo están en la documentación para desarrolladores (en inglés).

Requisitos de Procesamiento​

1. Respuesta del servidor y tiempo de espera​

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

  • Estado HTTP 200–299: 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.

IntentoRetraso después del anterior
11 minuto
22 minutos
34 minutos
48 minutos
515 minutos
615 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.