Documentation menu

Webhooks de saída

Os webhooks enviam eventos do Manisma para um endpoint HTTPS público da sua organização.

Configurar o endpoint

Abra como Admin Gestão → Integrações, crie um endpoint e escolha nome, URL e eventos. O signing secret é mostrado uma única vez. Guarde-o em segurança. Em caso de rotação, o novo secret passa a ser válido a partir da gravação.

O URL tem de:

  • utilizar HTTPS;
  • estar acessível publicamente via DNS;
  • utilizar a porta 443 ou 8443;
  • não conter nome de utilizador/palavra-passe no URL;
  • não apontar para localhost, IP privado, link-local ou metadados da cloud.

Utilize Teste para enviar um ping antes de ativar eventos reais.

Eventos

Evento Acionador
ping Teste manual do endpoint.
client.created Cliente criado.
client.updated Cliente alterado.
job.created Ordem de serviço criada.
job.planned Estado da ordem alterado para planeada.
job.completed Estado da ordem alterado para concluída.
job.cancelled Estado da ordem alterado para cancelada.
approval.approved O cliente aprovou uma proposta.
approval.declined O cliente recusou uma proposta.
workorder.completed Dados da ordem de trabalho pronta para faturação na conclusão.

Uma alteração noutro campo de uma ordem já concluída não volta a enviar job.completed; é o próprio estado que tem de mudar.

Envelope e cabeçalhos

{
  "event": "job.completed",
  "version": 1,
  "tenant_id": "tenant-uuid",
  "occurred_at": "2026-07-13T09:15:00.000Z",
  "data": {}
}

Cabeçalhos:

  • X-Manisma-Event: nome do evento;
  • X-Manisma-Delivery: id estável desta entrega lógica;
  • X-Manisma-Signature: sha256=<hex HMAC-SHA256> sobre o body em bruto exato.

Verifique o HMAC antes de fazer parsing do JSON e utilize uma comparação timing-safe:

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verify(rawBody, header, secret) {
  if (!header?.startsWith('sha256=')) return false
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
  const supplied = header.slice(7)
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(supplied, 'hex')
  return a.length === b.length && timingSafeEqual(a, b)
}

Payloads

Os eventos de cliente contêm id, customer_number, name, client_type, email e phone. As notas internas do cliente estão ausentes.

Os eventos de ordem de serviço contêm id/número da ordem, título, estado, prioridade, planeamento e objetos compactos para cliente, morada de serviço e técnico principal. Uma ordem não atribuída ou sem morada utiliza null.

Os eventos de aprovação contêm id da aprovação, job_id, estado, janela escolhida e hora da resposta. chosen_slot pode ser uma string JSON ou texto livre mais antigo; faça parsing defensivamente.

workorder.completed contém:

  • ordem de serviço e cliente compactos;
  • a visita concluída mais recente, se disponível;
  • materiais utilizados com SKU, preço de venda, quantidade e total da linha;
  • duração do trabalho;
  • total de materiais em EUR.

O preço do material é o preço de venda no momento em que o payload do webhook é construído, não necessariamente um preço fixado historicamente.

Entrega e retries

Os webhooks são at least once. Torne o seu recetor idempotente em X-Manisma-Delivery. Uma receção bem-sucedida é qualquer resposta 2xx. Os redirecionamentos não são seguidos e cada tentativa tem um timeout de 10 segundos.

Em caso de erro temporário, há no máximo três tentativas: imediata, após 60 segundos e mais 300 segundos depois. O mesmo delivery-id é mantido. Os retries correm, de momento, no processo de serviço e não sobrevivem a um redeploy; por isso, verifique também o registo de entregas em Integrações quanto a linhas pending pendentes.

Guarde primeiro o delivery-id na mesma transação que o seu processamento e responda depois rapidamente com 2xx. Mova o processamento lento para a sua própria fila.