Pular para o conteúdo

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.

  1. No exoCortex Core, abre Integrations → Webhooks. Precisas de uma função administrativa.
  2. Indica o URL que vai receber os eventos. Tem de ser https://.
  3. Escolhe os eventos. * significa todos os eventos.
  4. 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.
  5. Se o teu gateway o exigir, acrescenta cabeçalhos fixos, por exemplo um cabeçalho de autenticação.
  6. 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.

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.

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" }
}
}
  • id identifica o evento e é o mesmo para todas as subscrições que o recebem.
  • actor é o utilizador que causou o evento, ou system.
  • changes conté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ç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

Antes de confiares num evento, verifica a assinatura:

  1. Lê o corpo tal como chegou, sem interpretar e reescrever o JSON: os bytes mudariam.
  2. Rejeita o pedido se X-Exo-Timestamp estiver 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.
  3. Calcula o HMAC-SHA256 de timestamp + "." + body com o segredo da subscrição, em hexadecimal minúsculo.
  4. Compara o resultado, em tempo constante, com cada valor v1= de X-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.

  • Responde com qualquer código 2xx no 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 id ou X-Exo-Delivery para 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.