Saltar a contenido

Webhooks

Los webhooks de FrontEngine entregan notificaciones en tiempo real sobre eventos de email a su aplicación. Utilícelos para rastrear entregas, gestionar rebotes y medir el engagement.

Descripción general

Cuando ocurre un evento (email entregado, rebotado, abierto, etc.), FrontEngine envía una solicitud HTTP POST a la URL de webhook configurada con un payload JSON que describe el evento.

Eventos soportados

Eventos de mensaje:

Evento Disparador
MessageSent Mensaje enviado con éxito a un destinatario
MessageDelayed La entrega falló temporalmente, se reintentará
MessageDeliveryFailed El mensaje no se pudo entregar (fallo permanente)
MessageHeld El mensaje fue retenido para revisión
MessageBounced Se recibió una notificación de rebote para un mensaje previamente aceptado
MessageLinkClicked Un destinatario hizo clic en un enlace del email

Eventos de servidor:

Evento Disparador
SendLimitApproaching El servidor ha alcanzado el 90% de la capacidad de envío
SendLimitExceeded El servidor ha excedido su límite de envío
DomainDNSError Falló la validación DNS de SPF, DKIM, MX o return path para un dominio

Configuración

Configurando un webhook

  1. Vaya a Servicios > Email Transaccional > Webhooks
  2. Haga clic en Agregar Webhook
  3. Configure:
    • URL: Su endpoint HTTPS (p. ej., https://app.example.com/webhooks/email)
    • Eventos: Seleccione qué eventos recibir
    • Secreto: Un secreto compartido para la verificación del payload
  4. Haga clic en Guardar

HTTPS requerido

Las URLs de webhook deben utilizar HTTPS. Los endpoints HTTP no están admitidos por razones de seguridad.

Verificación de firmas de webhook

Cada solicitud de webhook incluye un encabezado de firma para la verificación del payload:

X-Webhook-Signature: sha256=a1b2c3d4e5f6...

Verifique la firma en su aplicación:

import hashlib
import hmac

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

Formato del payload

Todos los payloads de webhook son JSON. La estructura varía según el tipo de evento.

Payload de MessageSent

{
  "event": "MessageSent",
  "payload": {
    "status": "sent",
    "details": "Message accepted by remote server",
    "output": "250 OK",
    "time": 1.23,
    "sent_with_ssl": true,
    "timestamp": 1711806600,
    "message": {
      "id": 12345,
      "token": "abc123def456",
      "direction": "outgoing",
      "message_id": "<[email protected]>",
      "to": "[email protected]",
      "from": "[email protected]",
      "subject": "Order Confirmation #12345",
      "timestamp": 1711806590,
      "spam_status": "NotSpam",
      "tag": "order-confirmation"
    }
  }
}

Payload de MessageBounced

Incluye tanto el mensaje original como los detalles del rebote:

{
  "event": "MessageBounced",
  "payload": {
    "original_message": {
      "id": 12345,
      "token": "abc123def456",
      "to": "[email protected]",
      "from": "[email protected]",
      "subject": "Order Confirmation"
    },
    "bounce": {
      "id": 67890,
      "token": "xyz789",
      "to": "[email protected]",
      "from": "[email protected]",
      "subject": "Undelivered Mail Returned to Sender"
    }
  }
}

Payload de MessageLinkClicked

{
  "event": "MessageLinkClicked",
  "payload": {
    "url": "https://example.com/track-order/12345",
    "token": "abc123def456",
    "ip_address": "198.51.100.42",
    "user_agent": "Mozilla/5.0...",
    "message": {
      "id": 12345,
      "token": "abc123def456",
      "to": "[email protected]",
      "from": "[email protected]",
      "subject": "Order Confirmation #12345",
      "tag": "order-confirmation"
    }
  }
}

Payload de SendLimitApproaching / SendLimitExceeded

{
  "event": "SendLimitApproaching",
  "payload": {
    "server": {
      "uuid": "server-uuid",
      "name": "My Mail Server",
      "permalink": "my-mail-server",
      "organization": "My Organization"
    },
    "volume": 9000,
    "limit": 10000
  }
}

Payload de DomainDNSError

{
  "event": "DomainDNSError",
  "payload": {
    "domain": "example.com",
    "uuid": "domain-uuid",
    "dns_checked_at": "2026-03-30T14:00:00Z",
    "spf_status": "OK",
    "spf_error": null,
    "dkim_status": "Missing",
    "dkim_error": "No DKIM record found for selector",
    "mx_status": "OK",
    "mx_error": null,
    "return_path_status": "OK",
    "return_path_error": null,
    "server": {
      "uuid": "server-uuid",
      "name": "My Mail Server"
    }
  }
}

Mejores prácticas

Responda rápidamente

Su endpoint debe devolver un estado 200 en 5 segundos. Procese los datos del webhook de forma asíncrona — encole el payload y devuelva inmediatamente.

@app.post("/webhooks/email")
async def handle_webhook(request: Request):
    payload = await request.body()
    signature = request.headers.get("X-Webhook-Signature", "")

    if not verify_webhook(payload, signature, WEBHOOK_SECRET):
        return Response(status_code=401)

    # Enqueue for async processing
    await queue.put(payload)
    return Response(status_code=200)

Manejo de reintentos

Si su endpoint devuelve un estado distinto de 2xx o se agota el tiempo de espera, FrontEngine reintenta con backoff exponencial:

Intento Retraso
1.er reintento 1 minuto
2.º reintento 5 minutos
3.er reintento 30 minutos
4.º reintento 2 horas
5.º reintento 12 horas

Tras 5 intentos fallidos, el webhook se marca como fallido. Puede ver y reenviar los webhooks fallidos en el portal.

Deduplicación de eventos

Las entregas de webhook son al menos una vez. Su endpoint puede recibir el mismo evento más de una vez. Utilice los campos message_id y event para deduplicar.

Idempotencia

Diseñe su controlador de webhook para que sea idempotente — procesar el mismo evento dos veces debe producir el mismo resultado que procesarlo una vez.

Pruebas de webhooks

Utilice el botón Probar en el portal para enviar un evento de ejemplo a su endpoint. También puede utilizar herramientas como webhook.site durante el desarrollo para inspeccionar los payloads.

Próximos pasos