Ir al contenido

Webhooks salientes

Con un webhook saliente, exoCortex envía una solicitud POST a tu sistema cada vez que ocurre un evento al que te suscribiste: un ticket creado, un cambio de estado, un SLA por vencer. Así tu sistema reacciona de inmediato, sin consultar la API a intervalos.

  1. En exoCortex Core abre Integrations → Webhooks. Necesitas un rol administrativo.
  2. Indica la URL que recibirá los eventos. Debe ser https://.
  3. Elige los eventos. * significa todos los eventos.
  4. Elige el formato:
    • json: el formato estándar de exoCortex, que se describe en esta página;
    • datadog, pagerduty, slack-block: el formato nativo de esos servicios, para usarlo cuando la URL es la suya.
  5. Si tu gateway lo requiere, agrega encabezados fijos, por ejemplo un encabezado de autenticación.
  6. Guarda. Si no indicaste un secreto de firma, exoCortex genera uno (whsec_…) y lo muestra una sola vez: guárdalo, lo necesitas para verificar las firmas.

Con la acción de prueba envías a la suscripción el evento webhook.test y compruebas que tu sistema lo reciba.

Evento Cuándo llega
ticket.created Se creó un ticket
ticket.comment_added Se agregó un comentario
ticket.field_changed Cambió un campo
ticket.assignee_changed Cambió el responsable asignado
ticket.status_changed Cambió el estado
ticket.priority_changed Cambió la prioridad
ticket.sla_warning Un SLA está por vencer
ticket.sla_breach Un SLA venció
ticket.resolved El ticket se resolvió
ticket.closed El ticket se cerró
ticket.reopened El ticket se reabrió
ticket.link_created Se creó un vínculo entre tickets
ticket.link_deleted Se eliminó un vínculo entre tickets
routing.no_match Ninguna regla de enrutamiento encontró una cola para el ticket
routing.manual_routed Un operador enrutó el ticket a mano
webhook.test Enviaste un evento de prueba a la suscripción

Además de estos, puedes suscribirte por nombre a los eventos de las reglas de notificación de tu tenant, por ejemplo approval_requested.

exoCortex puede agregar nuevos tipos de evento: tu sistema debe ignorar los que no conozca.

Todos los eventos en formato json tienen la misma estructura:

{
"id": "evt_8a1f4c2e9b7d4e3f8a6b5c4d3e2f1a00",
"type": "ticket.status_changed",
"created_at": "2026-11-01T09:31:47.120Z",
"api_version": "1",
"tenant_id": "ACME",
"data": {
"ticket": {
"key": "ACME-1042",
"summary": "Payment gateway returns HTTP 502",
"status": "In Progress",
"priority": "High",
"issuetype": "IT-INC",
"assignee": "jane.doe",
"created": "2026-11-01T09:14:05.000Z",
"updated": "2026-11-01T09:31:47.000Z"
},
"actor": "jane.doe",
"changes": { "from": "Open", "to": "In Progress" }
}
}
  • id identifica el evento y es el mismo para todas las suscripciones que lo reciben.
  • actor es el usuario que causó el evento, o system.
  • changes contiene los detalles del evento: por ejemplo, el estado anterior y el nuevo, o el texto del comentario.

Hasta el 30 de junio de 2027, el cuerpo también contiene los campos obsoletos event, timestamp y source y una copia de los campos de data en el primer nivel. Un sistema nuevo debe leer solo los campos que se describen arriba.

Encabezado Ejemplo Significado
X-Exo-Timestamp 1793491200 Momento de la firma, en segundos Unix
X-Exo-Signature v1=5257a869… Firma del evento
X-Exo-Event ticket.created Tipo de evento, igual a type
X-Exo-Delivery dlv_0b9d… Identificador único del intento de entrega

Antes de confiar en un evento, verifica la firma:

  1. Lee el cuerpo tal como llegó, sin interpretar ni reescribir el JSON: los bytes cambiarían.
  2. Descarta la solicitud si X-Exo-Timestamp falta o difiere más de 300 segundos de tu reloj. Así bloqueas las solicitudes repetidas. Mantén el reloj sincronizado con NTP.
  3. Calcula el HMAC-SHA256 de timestamp + "." + body con el secreto de la suscripción, en hexadecimal en minúsculas.
  4. Compara el resultado, en tiempo constante, con cada valor v1= de X-Exo-Signature. Durante la rotación del secreto, el encabezado puede contener más de uno, separados por comas.

Ejemplo en Node.js:

import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyExoWebhook(rawBody, headers, secret, toleranceSec = 300) {
const ts = headers['x-exo-timestamp'];
const sigHeader = headers['x-exo-signature'] || '';
if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false;
const expected = Buffer.from(createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex'));
return sigHeader
.split(',')
.map((s) => s.trim())
.filter((s) => s.startsWith('v1='))
.some((s) => {
const got = Buffer.from(s.slice(3));
return got.length === expected.length && timingSafeEqual(got, expected);
});
}

Ejemplo en Python:

import hashlib, hmac, time
def verify_exo_webhook(raw_body: bytes, headers, secret: str, tolerance_sec: int = 300) -> bool:
ts = headers.get('X-Exo-Timestamp')
if not ts or abs(time.time() - int(ts)) > tolerance_sec:
return False
expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
sigs = [s.strip()[3:] for s in headers.get('X-Exo-Signature', '').split(',') if s.strip().startswith('v1=')]
return any(hmac.compare_digest(s, expected) for s in sigs)

El encabezado X-NH-Signature, que firma solo el cuerpo sin timestamp, es obsoleto y se eliminará el 30 de junio de 2027.

  • Responde con cualquier código 2xx en un máximo de 5 segundos. Si tienes que hacer un trabajo largo, ponlo en cola y responde de inmediato.
  • La entrega está garantizada al menos una vez: el mismo evento puede llegar más de una vez. Usa id o X-Exo-Delivery para reconocer los duplicados, por ejemplo guardando los identificadores que viste en los últimos 5 minutos.
  • Los eventos pueden llegar en un orden distinto al que ocurrieron: ordénalos con created_at.