Ir al contenido

Webhooks salientes

Con un webhook saliente, exoCortex envía una solicitud POST a tu sistema cada vez que se produce un evento al que te has suscrito: un ticket creado, un cambio de estado, un SLA a punto de vencer. Así tu sistema reacciona al momento, 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, descrito 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 exige, añade encabezados fijos, por ejemplo un encabezado de autenticación.
  6. Guarda. Si no has indicado 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 recibe.

Evento Cuándo llega
ticket.created Se ha creado un ticket
ticket.comment_added Se ha añadido un comentario
ticket.field_changed Ha cambiado un campo
ticket.assignee_changed Ha cambiado el asignatario
ticket.status_changed Ha cambiado el estado
ticket.priority_changed Ha cambiado la prioridad
ticket.sla_warning Un SLA está a punto de vencer
ticket.sla_breach Un SLA ha vencido
ticket.resolved El ticket se ha resuelto
ticket.closed El ticket se ha cerrado
ticket.reopened El ticket se ha reabierto
ticket.link_created Se ha creado un vínculo entre tickets
ticket.link_deleted Se ha eliminado un vínculo entre tickets
routing.no_match Ninguna regla de enrutamiento ha encontrado una cola para el ticket
routing.manual_routed Un operador ha enrutado el ticket a mano
webhook.test Has enviado 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 añadir 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 ha causado el evento, o bien 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 contiene también los campos obsoletos event, timestamp y source y una copia de los campos de data en el primer nivel. Un sistema nuevo solo debe leer los campos descritos 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 fiarte de un evento, verifica la firma:

  1. Lee el cuerpo tal como ha llegado, sin interpretar ni reescribir el JSON: cambiarían los bytes.
  2. Descarta la solicitud si falta X-Exo-Timestamp o si se aleja 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 solo firma el cuerpo sin marca de tiempo, está obsoleto y se eliminará el 30 de junio de 2027.

  • Responde con cualquier código 2xx en un plazo de 5 segundos. Si tienes que hacer un trabajo largo, ponlo en cola y responde al momento.
  • 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 vistos en los últimos 5 minutos.
  • Los eventos pueden llegar en un orden distinto al que se produjeron: ordénalos con created_at.