Webhooks de saída
Com um webhook de saída, o exoCortex envia um pedido POST ao teu sistema sempre que ocorre um evento a que te subscreveste: um ticket criado, uma mudança de estado, um SLA prestes a expirar. Assim, o teu sistema reage de imediato, sem consultar a API a intervalos.
Criar uma subscrição
Seção intitulada “Criar uma subscrição”- No exoCortex Core, abre Integrations → Webhooks. Precisas de uma função administrativa.
- Indica o URL que vai receber os eventos. Tem de ser
https://. - Escolhe os eventos.
*significa todos os eventos. - Escolhe o formato:
json: o formato padrão do exoCortex, descrito nesta página;datadog,pagerduty,slack-block: o formato nativo desses serviços, a usar quando o URL é deles.
- Se o teu gateway o exigir, acrescenta cabeçalhos fixos, por exemplo um cabeçalho de autenticação.
- Guarda. Se não indicaste um segredo de assinatura, o exoCortex gera um (
whsec_…) e mostra-o uma única vez: guarda-o, precisas dele para verificar as assinaturas.
Com a ação de teste envias à subscrição o evento webhook.test e verificas se o teu sistema o recebe.
Eventos
Seção intitulada “Eventos”| Evento | Quando chega |
|---|---|
ticket.created |
Foi criado um ticket |
ticket.comment_added |
Foi adicionado um comentário |
ticket.field_changed |
Mudou um campo |
ticket.assignee_changed |
Mudou o responsável atribuído |
ticket.status_changed |
Mudou o estado |
ticket.priority_changed |
Mudou a prioridade |
ticket.sla_warning |
Um SLA está prestes a expirar |
ticket.sla_breach |
Um SLA expirou |
ticket.resolved |
O ticket foi resolvido |
ticket.closed |
O ticket foi fechado |
ticket.reopened |
O ticket foi reaberto |
ticket.link_created |
Foi criada uma ligação entre tickets |
ticket.link_deleted |
Foi removida uma ligação entre tickets |
routing.no_match |
Nenhuma regra de encaminhamento encontrou uma fila para o ticket |
routing.manual_routed |
Um operador encaminhou o ticket manualmente |
webhook.test |
Enviaste um evento de teste à subscrição |
Além destes, podes subscrever pelo nome os eventos das regras de notificação do teu tenant, por exemplo approval_requested.
O exoCortex pode acrescentar novos tipos de evento: o teu sistema deve ignorar os que não conhece.
Formato do evento
Seção intitulada “Formato do evento”Todos os eventos em formato json têm a mesma estrutura:
{ "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" } }}ididentifica o evento e é o mesmo para todas as subscrições que o recebem.actoré o utilizador que causou o evento, ousystem.changescontém os detalhes do evento: por exemplo, o estado anterior e o novo, ou o texto do comentário.
Até 30 de junho de 2027, o corpo contém também os campos descontinuados event, timestamp e source e uma cópia dos campos de data no primeiro nível. Um sistema novo deve ler apenas os campos descritos acima.
Cabeçalhos
Seção intitulada “Cabeçalhos”| Cabeçalho | Exemplo | Significado |
|---|---|---|
X-Exo-Timestamp |
1793491200 |
Momento da assinatura, em segundos Unix |
X-Exo-Signature |
v1=5257a869… |
Assinatura do evento |
X-Exo-Event |
ticket.created |
Tipo de evento, igual a type |
X-Exo-Delivery |
dlv_0b9d… |
Identificador único da tentativa de entrega |
Verificar a assinatura
Seção intitulada “Verificar a assinatura”Antes de confiares num evento, verifica a assinatura:
- Lê o corpo tal como chegou, sem interpretar e reescrever o JSON: os bytes mudariam.
- Rejeita o pedido se
X-Exo-Timestampestiver em falta ou se afastar mais de 300 segundos do teu relógio. Assim bloqueias os pedidos repetidos. Mantém o relógio sincronizado por NTP. - Calcula o HMAC-SHA256 de
timestamp + "." + bodycom o segredo da subscrição, em hexadecimal minúsculo. - Compara o resultado, em tempo constante, com cada valor
v1=deX-Exo-Signature. Durante a rotação do segredo, o cabeçalho pode conter mais do que um, separados por vírgulas.
Exemplo em 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); });}Exemplo em 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)O cabeçalho X-NH-Signature, que assina apenas o corpo sem timestamp, está descontinuado e será removido a 30 de junho de 2027.
Responder e gerir os duplicados
Seção intitulada “Responder e gerir os duplicados”- Responde com qualquer código
2xxno prazo de 5 segundos. Se tiveres de fazer um trabalho demorado, coloca-o em fila e responde de imediato. - A entrega é garantida pelo menos uma vez: o mesmo evento pode chegar mais do que uma vez. Usa
idouX-Exo-Deliverypara reconhecer os duplicados, por exemplo guardando os identificadores vistos nos últimos 5 minutos. - Os eventos podem chegar por uma ordem diferente daquela em que ocorreram: ordena-os por
created_at.