Avisos por Slack, Teams y webhook

Además del correo, los avisos de falla y de normalizado pueden llegar a Slack, Microsoft Teams o a tu propio sistema (webhook). Cada canal recibe exactamente los mismos avisos que el correo: uno cuando algo empieza a fallar y otro cuando lleva varios minutos seguidos funcionando bien. Las caídas que vuelven dentro de ese plazo son parte del mismo aviso.

Disponible en los planes Básico y Profesional (hasta 5 canales activos). Los configura un administrador de la empresa en la pestaña Canales, con el segundo factor activo.

Slack

  1. En Slack, crea una app (o usa una existente) y activa Incoming Webhooks.
  2. Elige el canal y copia la dirección, que empieza con https://hooks.slack.com/services/.
  3. En Canales, elige Slack, ponle un nombre, pega la dirección y presiona Agregar. Usa Enviar prueba para comprobarlo.

Microsoft Teams

Teams recibe avisos con la app Workflows (los antiguos conectores de Microsoft 365 están en retiro).

  1. En el canal de Teams, abre Más opciones (…) → Workflows y elige la plantilla Send webhook alerts to a channel.
  2. Guarda el flujo y copia la dirección que te entrega.
  3. En Canales, elige Microsoft Teams y pega la dirección.

Webhook a tu sistema

Enviamos un POST con un JSON a una dirección https tuya. Solo se aceptan direcciones públicas, no seguimos redirecciones y esperamos hasta 10 segundos por la respuesta. Responde con cualquier código 2xx. Si respondes 408, 429 o 5xx, o no respondemos a tiempo, reintentamos con espera creciente durante 2 horas; cualquier otro 4xx detiene los reintentos de ese aviso.

Cabeceras:

  • Content-Type: application/json
  • X-Tecdex-Event: el tipo de evento.
  • X-Tecdex-Delivery: identificador del aviso. Es el mismo en cada reintento: úsalo para no procesar dos veces el mismo aviso.
  • X-Tecdex-Signature: firma, con la forma t=<segundos unix>,v1=<firma>.

Ejemplo de aviso de falla:

{
  "version": 1,
  "id": "6f1c2d1e-9a7b-4c51-8f0e-2b1f5c3a9d10",
  "event": "failure",
  "account": "acme",
  "system": "Sitio web",
  "title": "ACME · Sitio web con falla",
  "text": "ACME · Sitio web con falla\nQué falla: Disponibilidad\nDesde: 26-10-2026 09:00 (UTC-03:00)\n…",
  "language": "es",
  "url": "https://monitoreo.tecdex.net/c/acme/incidentes",
  "sent_at": "2026-10-26T12:00:05Z",
  "since": "2026-10-26T12:00:00Z",
  "recovery_wait_minutes": 10,
  "checks": [{"name": "Disponibilidad", "detail": "No responde dentro del tiempo límite"}]
}

Campos:

  • version: versión del formato (hoy 1). Podemos agregar campos nuevos sin cambiar la versión; nunca cambiamos ni quitamos los existentes.
  • id: identificador único del aviso (igual a la cabecera X-Tecdex-Delivery).
  • event: failure (empezó a fallar), failure_update (falla además otra verificación del mismo sistema), recovery (normalizado) o test (mensaje de prueba).
  • account: identificador de tu empresa. system: el sistema monitoreado (vacío en test).
  • title y text: el aviso redactado en el idioma del canal (language: es, en o pt).
  • url: dónde ver el detalle en el portal (puede ser null). sent_at: cuándo lo enviamos (UTC).
  • En failure y failure_update: since (desde cuándo falla, UTC), recovery_wait_minutes (minutos seguidos bien para avisar normalizado) y checks con name y detail (puede ser null).
  • En recovery: started_at y ended_at (período con fallas, UTC), duration_seconds, failures (caídas en el período) y checks con name y failures.

Verificar la firma

Al crear el webhook te mostramos una sola vez su secreto (empieza con whsec_). Guárdalo en tu sistema. Si lo pierdes o se filtra, usa Rotar secreto: el anterior deja de servir.

Calcula HMAC-SHA256 con el secreto sobre el texto <t>.<cuerpo> (el cuerpo tal como llegó, sin volver a armar el JSON) y compáralo con v1. Rechaza además los avisos con t de más de 5 minutos de diferencia con tu reloj, para que nadie pueda repetir uno viejo. Ejemplo en Python:

import hashlib
import hmac
import time


def verificar(secreto, cabecera, cuerpo, ahora=None, tolerancia=300):
    """secreto: el whsec_… · cabecera: X-Tecdex-Signature · cuerpo: bytes tal como llegaron."""
    try:
        partes = dict(p.split("=", 1) for p in cabecera.split(","))
        t = int(partes["t"])
    except (ValueError, KeyError):
        return False
    if abs((ahora or time.time()) - t) > tolerancia:
        return False
    esperado = hmac.new(secreto.encode(), f"{t}.".encode() + cuerpo, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado, partes.get("v1", ""))

Ingresar · Crear cuenta