BitelioBitelio
Guides

Webhooks

Envía eventos de Bitelio a tus propios endpoints en tiempo real usando pasos Webhook de flujo de trabajo

Cuando algo ocurre en Bitelio — un correo rebota, llega una queja de spam, se dispara un evento personalizado — puedes hacer que una solicitud HTTP llegue a tu aplicación en tiempo real. El mecanismo es un flujo de trabajo que contiene un paso Webhook que envía los datos del evento a un endpoint que tú controlas.

¿Buscas una entrega firmada y con reintentos?

Esta página cubre el paso Webhook de flujo de trabajo: flexible, pero sin firma, sin historial de entregas, reenvío ni disyuntor de circuito, y sus reintentos solo cubren el paso en el que reentra un job reintentado (ver "Recibir webhooks de forma segura" más abajo). Para una alternativa gestionada centralmente — Bitelio firma cada petición, reintenta de extremo a extremo, y degrada un endpoint que sigue fallando — ver Webhooks, que se registran en Ajustes → Webhooks.

Cómo funciona

Los webhooks se apoyan sobre el sistema de flujos de trabajo. La cadena se ve así:

  1. Ocurre algún evento dentro de Bitelio (un correo rebota, un contacto se suscribe, se rastrea un evento personalizado, ...)
  2. Se dispara un flujo de trabajo que escucha ese evento
  3. Su paso Webhook se dispara, entregando una solicitud HTTP con los datos relevantes a tu URL

Como cualquier evento rastreado puede disparar un flujo de trabajo, cualquiera de ellos — generado por el sistema o tus propios eventos personalizados — puede llegarte como un webhook.

Eventos internos

Bitelio mismo rastrea un conjunto de eventos internos y los ofrece como disparadores de flujo de trabajo. No puedes registrar estos mediante la API; solo el sistema los emite.

Eventos de correo

EventoDescripción
email.sentUn correo se envió con éxito
email.deliveryUn correo se entregó al destinatario
email.openUn contacto abrió un correo por primera vez
email.clickUn contacto hizo clic en un enlace de un correo por primera vez
email.bounceUn correo rebotó (rebote duro o suave)
email.complaintUn contacto marcó un correo como spam
email.receivedSe recibió un correo en tu dominio verificado (requiere configuración de correo entrante)

Eventos de contacto

EventoDescripción
contact.subscribedEl estado de suscripción de un contacto cambió a suscrito
contact.unsubscribedEl estado de suscripción de un contacto cambió a no suscrito

Eventos de segmento

EventoDescripción
segment.<name>.entryUn contacto entró a un segmento
segment.<name>.exitUn contacto salió de un segmento

Nombres de eventos de segmento

La parte <name> es el nombre del segmento en forma de slug. Un segmento llamado "VIP Users" por lo tanto emite segment.vip-users.entry y segment.vip-users.exit.

Configurar un webhook

Crea el flujo de trabajo

Ve a Flujos de trabajo en el panel e inicia uno nuevo, eligiendo el evento que te interesa como su disparador. Para recibir notificaciones sobre rebotes, por ejemplo, establece el disparador en email.bounce.

Añade un paso Webhook

Adjunta un paso Webhook después del disparador y completa sus ajustes:

  • URL: Dónde en tu servidor debe aterrizar el webhook (p. ej. https://api.example.com/webhooks/bitelio)
  • Método: El verbo HTTP a usar. POST es el predeterminado y la elección correcta para la mayoría de situaciones.
  • Encabezados (opcional): Encabezados adicionales para la solicitud, dados como JSON — útil para autenticación.
{
  "Authorization": "Bearer your-secret-token"
}
  • Cuerpo (opcional): Un cuerpo de solicitud personalizado. Déjalo vacío y Bitelio envía la carga útil por defecto documentada abajo; suministra uno y reemplaza por completo esa carga por defecto, codificada en JSON antes de enviarse.

Usa variables en la solicitud (opcional)

La interpolación {{variable}} funciona en la url, en los valores de encabezado, y en el body. El alcance de variables coincide con el que obtienen las plantillas SEND_EMAIL, con una adición exclusiva de los webhooks: un espacio de nombres event que lleva el payload del evento disparador:

VariableValor
{{id}}, {{email}}El ID y el correo del contacto.
{{<key>}} (nivel superior)Cualquier clave del JSON data del contacto (p. ej. {{firstName}}, {{plan}}).
{{data.<key>}}Los mismos datos de contacto, dirigidos vía el espacio de nombres data.
{{event.<key>}}Exclusivo de webhooks. Campos del payload del evento disparador (p. ej. {{event.subject}}).
{{<key>}} (del contexto de ejecución)Claves pasadas como context al iniciar una ejecución MANUAL.
{{unsubscribeUrl}}, {{subscribeUrl}}, {{manageUrl}}URL de gestión de suscripción por contacto.

Dos cosas no son parametrizables: el method HTTP, que tiene que ser un verbo literal (GET, POST, PUT, PATCH, DELETE), y el esquema de la URL, que debe escribirse como http:// o https:// — los marcadores pueden aparecer en cualquier otra parte de la URL, solo no en lugar del esquema.

Ejemplo — reenviar un evento de contacto a tu propia API, parametrizado con datos del contacto:

URL

https://api.example.com/users/{{id}}/events

Encabezados

{
  "Authorization": "Bearer your-secret-token"
}

Cuerpo

{
  "email": "{{email}}",
  "plan": "{{plan}}",
  "referrer": "{{event.referrer}}"
}

Habilita el flujo de trabajo

Activa el flujo de trabajo terminado. A partir de ahí, cada ocurrencia del evento disparador produce una solicitud de webhook.

Carga útil del webhook

Sin ningún cuerpo personalizado configurado, la carga útil por defecto que envía Bitelio es una solicitud JSON con esta forma:

{
  "contact": {
    "email": "user@example.com",
    "subscribed": true,
    "data": {
      "name": "John",
      "plan": "pro"
    }
  },
  "workflow": {
    "id": "wf_abc123",
    "name": "Bounce Notifications"
  },
  "execution": {
    "id": "exec_xyz789",
    "startedAt": "2025-01-15T10:30:00.000Z"
  },
  "event": {
    "subject": "Welcome to Bitelio",
    "from": "hello@example.com",
    "fromName": "Bitelio Team",
    "messageId": "ses-message-id",
    "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
    "templateId": null,
    "campaignId": "camp_abc123",
    "sourceType": "CAMPAIGN",
    "bounceType": "Permanent",
    "bouncedAt": "2025-01-15T10:30:00.000Z"
  }
}

Cualquier dato que haya acompañado al evento disparador termina en el campo event — así que su forma exacta varía según el tipo de evento.

Datos de evento por tipo

Eventos de correo

Un conjunto común de campos base aparece en la mayoría de los eventos de correo:

Prop

Type

Además de esta base, cada tipo de evento añade sus propios campos, mostrados por pestaña abajo. Recuerda que los campos base (subject, from, fromName, messageId, emailId, templateId, campaignId, sourceType) acompañan a todo evento de correo junto a los específicos del evento.

{
  "subject": "Welcome to Bitelio",
  "from": "hello@example.com",
  "fromName": "Bitelio Team",
  "messageId": "ses-message-id",
  "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
  "templateId": null,
  "campaignId": null,
  "sourceType": "TRANSACTIONAL",
  "sentAt": "2025-01-15T10:30:00.000Z"
}

Prop

Type

{
  "subject": "Welcome to Bitelio",
  "from": "hello@example.com",
  "fromName": "Bitelio Team",
  "messageId": "ses-message-id",
  "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
  "templateId": null,
  "campaignId": "camp_abc123",
  "sourceType": "CAMPAIGN",
  "deliveredAt": "2025-01-15T10:30:05.000Z"
}

Prop

Type

{
  "subject": "Welcome to Bitelio",
  "from": "hello@example.com",
  "fromName": "Bitelio Team",
  "messageId": "ses-message-id",
  "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
  "templateId": null,
  "campaignId": null,
  "sourceType": "TRANSACTIONAL",
  "openedAt": "2025-01-15T11:00:00.000Z",
  "opens": 1,
  "isFirstOpen": true
}

Prop

Type

{
  "subject": "Welcome to Bitelio",
  "from": "hello@example.com",
  "fromName": "Bitelio Team",
  "messageId": "ses-message-id",
  "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
  "templateId": null,
  "campaignId": null,
  "sourceType": "TRANSACTIONAL",
  "link": "https://example.com/pricing",
  "clickedAt": "2025-01-15T11:05:00.000Z",
  "clicks": 1,
  "isFirstClick": true
}

Prop

Type

Rebote permanente:

{
  "subject": "Welcome to Bitelio",
  "from": "hello@example.com",
  "fromName": "Bitelio Team",
  "messageId": "ses-message-id",
  "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
  "templateId": null,
  "campaignId": null,
  "sourceType": "TRANSACTIONAL",
  "bounceType": "Permanent",
  "bouncedAt": "2025-01-15T10:31:00.000Z"
}

Rebote transitorio (suave):

{
  "subject": "Welcome to Bitelio",
  "from": "hello@example.com",
  "fromName": "Bitelio Team",
  "messageId": "ses-message-id",
  "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
  "templateId": null,
  "campaignId": null,
  "sourceType": "TRANSACTIONAL",
  "bounceType": "Transient",
  "transientBounce": true
}

Prop

Type

Impacto en la tasa de rebote

Solo los rebotes Permanent cuentan hacia la tasa de rebote de tu proyecto y disparan la baja automática del contacto. Los rebotes Transient se rastrean solo para visibilidad.

{
  "subject": "Welcome to Bitelio",
  "from": "hello@example.com",
  "fromName": "Bitelio Team",
  "messageId": "ses-message-id",
  "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
  "templateId": null,
  "campaignId": null,
  "sourceType": "TRANSACTIONAL",
  "complainedAt": "2025-01-15T10:35:00.000Z"
}

Prop

Type

Se dispara cuando llega correo a tu dominio verificado — la configuración se cubre en Recepción de correos.

{
  "messageId": "ses-message-id",
  "from": "sender@example.com",
  "fromHeader": "Jane Smith <sender@example.com>",
  "to": "support@yourdomain.com",
  "subject": "Re: Your question",
  "timestamp": "2025-01-15T10:30:00.000Z",
  "recipients": ["support@yourdomain.com"],
  "hasContent": true,
  "body": "<html><body>This is the email body content...</body></html>",
  "spamVerdict": "PASS",
  "virusVerdict": "PASS",
  "spfVerdict": "PASS",
  "dkimVerdict": "PASS",
  "dmarcVerdict": "PASS",
  "processingTimeMillis": 142
}

Prop

Type

Eventos de contacto

Por defecto, contact.subscribed y contact.unsubscribed no vienen con ningún dato de evento — event es simplemente {}.

Hay una excepción: cuando un rebote o una queja causó la baja automáticamente, event lleva un reason:

{
  "reason": "bounce"
}

Prop

Type

Eventos de segmento

segment.<name>.entry y segment.<name>.exit llevan ambos:

{
  "segmentId": "seg_abc123",
  "segmentName": "VIP Users"
}

Prop

Type

Eventos personalizados

Para eventos que rastreas tú mismo a través de la API, la carga útil es el objeto data que hayas suministrado en la llamada track.

Sin datos de evento

Los eventos sin ningún dato asociado producen un objeto vacío {} en el campo event.

Correlacionar webhooks con solicitudes de envío

Todo evento de correo lleva un emailId — el mismo ID de registro de correo de Bitelio que devuelve POST /v1/send. Eso te da una unión directa entre eventos de webhook y las llamadas a la API que los causaron.

Ejemplo de flujo de trabajo:

  1. Enviar correo vía API:
POST /v1/send
{
  "to": "user@example.com",
  "subject": "Welcome",
  "body": "Hello!"
}

Response:
{
  "success": true,
  "data": {
    "emails": [
      {
        "contact": {"id": "cnt_abc", "email": "user@example.com"},
        "email": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf"
      }
    ]
  }
}
  1. Guarda el ID email (ac32f08e-c6b9-45d3-9824-a73dff1e3bbf) en tu base de datos

  2. Cuando se disparen eventos de webhook (p. ej., email.open, email.bounce), empárejalos usando event.emailId:

{
  "event": {
    "emailId": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf",
    "messageId": "ses-message-id",
    "openedAt": "2025-01-15T11:00:00.000Z"
  }
}

Con esta unión en su lugar, no hay razón para emparejar por correo del contacto más marca de tiempo, ni para suscribirte a webhooks de email.sent solo para capturar el messageId del proveedor.

Casos de uso comunes

Monitorización de rebotes y quejas

Reenvía rebotes y quejas a tu aplicación con un flujo de trabajo disparado en email.bounce o email.complaint — una forma directa de mantener tu propia base de datos alineada con los estados de contacto de Bitelio.

Los pasos colocados antes del webhook te permiten afinar el comportamiento:

  • Condición: Restringe el webhook a rebotes duros inspeccionando bounceType
  • Retraso: Inserta una pausa corta para que los eventos relacionados puedan procesarse juntos
  • Actualizar contacto: Adjunta metadatos al contacto antes de que salga el webhook

Sincronizar bajas

Un flujo de trabajo disparado en contact.unsubscribed puede alertar a tu aplicación cada vez que alguien se da de baja — el patrón estándar para mantener el estado de suscripción consistente entre sistemas.

Reenvío de eventos personalizados

Los eventos que rastreas tú mismo (digamos user.signup u order.completed) pueden retransmitirse a otros servicios mediante webhooks. En efecto, Bitelio se convierte en un enrutador de eventos: una llamada a track, entrega a tantos endpoints como quieras.

Recibir webhooks de forma segura

Algunas propiedades del paso Webhook de Bitelio importan al diseñar el endpoint que lo recibe:

  • Método: POST con Content-Type: application/json por defecto; cada paso puede sobrescribir el método.

  • Tiempo de espera: las solicitudes se cortan después de 10 segundos. Si tu procesamiento tarda más, acepta la solicitud, encola el trabajo, y responde 2xx de inmediato.

  • Redirecciones: se siguen como máximo 5 redirecciones, y cada salto se vuelve a comprobar contra las reglas SSRF de abajo.

  • Se requiere URL pública: el endpoint tiene que ser alcanzable desde internet público — los destinos privados e internos (loopback, rangos RFC 1918, etc.) nunca reciben entregas.

  • Esquemas: solo http:// y https://. Prefiere HTTPS.

  • Reintentos, pero limitados a un paso: una respuesta que no sea 2xx o un tiempo de espera lanza una excepción, y la cola reintenta el job (3 intentos en total, con espera exponencial). Ese reintento vuelve a ejecutar el mismo paso para el que se encoló el job — un paso Webhook al que se llega más adelante en la misma ejecución, sin un DELAY o WAIT_FOR_EVENT de por medio, no tiene reintento propio; un fallo ahí termina el intento de inmediato. En cualquier caso, haz que tu manejador sea idempotente, y configura el onError del paso a continue (por defecto es stop) si un fallo aquí no debería detener el recorrido del contacto.

  • Verifica la autenticidad con un secreto compartido: pon un encabezado secreto en el paso webhook y valídalo del lado del servidor:

    // Paso Webhook → Encabezados
    {
      "Authorization": "Bearer your-shared-secret"
    }

    Cada solicitud de ese paso lleva el secreto; rótalo como lo harías con cualquier credencial compartida. Esto es mejor que la lista blanca de IP, ya que las IP de salida están sujetas a cambios.

Añadir condiciones y retrasos

Como los webhooks son pasos de flujo de trabajo ordinarios, se combinan con todo lo demás que ofrece el sistema de flujos de trabajo:

  • Un paso Condición condiciona el webhook a cualquier criterio que elijas (p. ej. solo contactos en un plan particular)
  • Un paso Esperar evento retiene el webhook hasta que llegue un evento de seguimiento (p. ej. ver si un contacto que rebotó se resuscribe primero)
  • Un paso Retraso inserta un margen de tiempo antes del webhook