Webhooks
Os webhooks permitem que os seus sistemas recebam notificações HTTP POST em tempo real quando ocorrem eventos de fila — uma senha entra, é chamada, é concluída, e assim por diante.
Introdução
Seção intitulada “Introdução”- Abra admin.jonot.io/settings/integrations.
- Clique em Adicionar endpoint.
- Introduza um URL HTTPS público que o seu servidor esteja a escutar.
- Escolha a que eventos quer subscrever.
- Clique em Guardar.
Guarde o seu segredo de assinatura — é mostrado apenas uma vez. Para verificar se o seu endpoint está acessível, use o botão Enviar pedido de teste na página de edição do endpoint.
Tipos de evento
Seção intitulada “Tipos de evento”| Nome do evento | Quando dispara |
|---|---|
ticket.joined | Um cliente entra numa fila |
ticket.called | Um membro do pessoal chama uma senha ao balcão de atendimento |
ticket.completed | Uma senha é marcada como concluída |
ticket.cancelled | Um cliente ou membro do pessoal cancela uma senha |
ticket.skipped | Uma senha é saltada (um adiamento chamável de novo) |
ticket.no_show | Uma senha chamada ou saltada é confirmada como ausência |
queue.status_changed | O estado de uma fila muda (ACTIVE, PAUSED ou CLOSED) |
Formato do payload
Seção intitulada “Formato do payload”Toda a entrega é um HTTP POST com:
Content-Type: application/jsonX-Jonot-Event: ticket.calledX-Jonot-Delivery-Id: <uuid>X-Jonot-Timestamp: 2024-06-01T12:00:00.000ZX-Jonot-Signature: v1,<base64-hmac-sha256>X-Jonot-Payload-Version: v1O corpo predefinido é um envelope JSON. A forma do campo payload varia consoante o evento:
ticket.joined
{ "deliveryId": "<uuid>", "event": "ticket.joined", "timestamp": "2024-06-01T12:00:00.000Z", "org": { "id": "org_…", "name": "My Org" }, "payload": { "ticket": { "id": "tkt_…", "queueId": "q_…" }, "queue": { "id": "q_…", "name": "Main Queue" }, "location": { "id": "loc_…", "name": "Downtown" } }}ticket.called / ticket.completed / ticket.skipped / ticket.no_show
{ "deliveryId": "<uuid>", "event": "ticket.called", "timestamp": "2024-06-01T12:00:00.000Z", "org": { "id": "org_…", "name": "My Org" }, "payload": { "ticket": { "id": "tkt_…", "number": 42, "status": "CALLED", "queueId": "q_…", "createdAt": "2024-06-01T11:58:00.000Z", "updatedAt": "2024-06-01T12:00:00.000Z" } }}ticket.cancelled
{ "deliveryId": "<uuid>", "event": "ticket.cancelled", "timestamp": "2024-06-01T12:00:00.000Z", "org": { "id": "org_…", "name": "My Org" }, "payload": { "ticketId": "tkt_…", "queueId": "q_…" }}queue.status_changed
{ "deliveryId": "<uuid>", "event": "queue.status_changed", "timestamp": "2024-06-01T12:00:00.000Z", "org": { "id": "org_…", "name": "My Org" }, "payload": { "queueId": "q_…", "status": "PAUSED" }}Verificar a assinatura
Seção intitulada “Verificar a assinatura”O Jonot assina cada entrega com HMAC-SHA256 sobre a cadeia:
<deliveryId>.<timestamp>.<body>Para verificar em Node.js (≥18):
import { createHmac, timingSafeEqual } from "node:crypto";
/** * Returns true when the signature header is valid and the timestamp is * within 5 minutes of now. Throws for malformed input. */function verifySignature(secret, deliveryId, timestamp, body, header) { // Replay-attack guard: reject deliveries older than 5 minutes. const ageMs = Date.now() - new Date(timestamp).getTime(); if (Math.abs(ageMs) > 5 * 60 * 1000) return false;
const expected = "v1," + createHmac("sha256", secret) .update(`${deliveryId}.${timestamp}.${body}`) .digest("base64");
// timingSafeEqual prevents timing-oracle attacks. // Buffers must be the same length — if lengths differ the signature is // invalid, but we still compare a dummy value to keep constant time. const expectedBuf = Buffer.from(expected); const headerBuf = Buffer.from(header); if (expectedBuf.length !== headerBuf.length) return false; return timingSafeEqual(expectedBuf, headerBuf);}Modelos de payload
Seção intitulada “Modelos de payload”Pode substituir o corpo JSON predefinido por um modelo personalizado. Os modelos usam uma sintaxe Mustache-lite:
{{ path.to.value }}— substituído e escapado de acordo com o tipo de conteúdo: escapado como cadeia JSON paraapplication/json, codificado em percentagem paraapplication/x-www-form-urlencoded, em bruto paratext/plain{{{ path.to.value }}}— substituído em bruto (sem escape)
Exemplo de modelo para application/json (subscrito a ticket.joined):
{ "type": "{{ event }}", "ticketId": "{{ payload.ticket.id }}", "queueName": "{{ payload.queue.name }}"}Editor de modelos
Seção intitulada “Editor de modelos”O campo de modelo de payload é um editor de código completo com:
- Realce de sintaxe e correspondência de parênteses para modelos JSON.
- Paleta de variáveis — uma linha sempre visível de botões de inserção, um por cada caminho de variável disponível para os eventos selecionados. Clique num botão para inserir um token
{{ path }}na posição do cursor. - Validação em tempo real — à medida que escreve, o editor verifica tanto a estrutura JSON (quando o tipo de conteúdo é
application/jsone não existem etiquetas{{{ }}}em bruto) como os caminhos de variável. Os diagnósticos aparecem como marcadores incorporados no editor e como uma faixa de resumo por baixo:- Erro — um caminho desconhecido em todos os eventos selecionados.
- Aviso — um caminho que existe apenas para alguns dos eventos selecionados (ficará vazio nas entregas dos outros eventos).
A validação também é executada no momento de guardar, no servidor — o feedback do editor espelha exatamente as regras do servidor.
Repetições de entrega
Seção intitulada “Repetições de entrega”As entregas falhadas (não-2xx ou erro de ligação) são repetidas até 3 vezes com espera exponencial pelo Cloudflare Queues. Depois de 3 falhas, a entrega vai para a fila de mensagens mortas e o contador de falhas consecutivas do endpoint incrementa.
Assim que um endpoint acumula 20 falhas consecutivas, é desativado automaticamente. Reative-o a partir da página de edição do endpoint; o contador repõe-se a zero.
Histórico de entregas
Seção intitulada “Histórico de entregas”O separador Entregas de cada endpoint mostra os últimos 30 dias de tentativas de entrega: tipo de evento, estado HTTP, número de tentativas e registo de data/hora. Use o botão Carregar mais para percorrer registos mais antigos.
Limites
Seção intitulada “Limites”| Limite | Valor |
|---|---|
| Endpoints por organização | 5 |
| Cabeçalhos personalizados por endpoint | 10 |
| Comprimento do valor do cabeçalho | 1024 bytes |
| Tamanho do modelo de payload | 16 KB |
| Tempo limite de entrega | 10 s |
| Retenção do histórico de entregas | 30 dias |
| Taxa máxima de entrega por organização | 120 / 60 s |
Rodar o segredo de assinatura
Seção intitulada “Rodar o segredo de assinatura”- Abra a página de edição do endpoint.
- Clique em Rodar segredo de assinatura.
- Confirme a rotação na caixa de diálogo.
- Copie o novo segredo imediatamente — é mostrado apenas uma vez e não pode ser recuperado. Se fechar a caixa de diálogo sem o guardar, terá de rodar novamente para obter um novo valor em texto simples.
- Atualize o seu servidor para verificar as assinaturas com o novo segredo.
Não há nenhuma janela de sobreposição. O segredo antigo deixa de verificar assim que confirmar a rotação. Planeie implementar o novo segredo no seu recetor imediatamente após a rotação.
A interface mostra Segredo ativo: ····XXXX (os últimos quatro carateres) junto ao botão Rodar, para que possa verificar qual o segredo atualmente em vigor — útil para confirmar que o seu recetor e o servidor estão sincronizados após uma rotação.
Boas práticas do segredo de assinatura
Seção intitulada “Boas práticas do segredo de assinatura”Quando rodar:
- Suspeita de comprometimento: o segredo apareceu num ficheiro de registo, foi partilhado com um funcionário que saiu, ou foi capturado numa gravação de ecrã.
- Higiene de rotina: rodar periodicamente limita o impacto de uma exposição não detetada.
- Depois de qualquer alteração a que serviços podem ler o segredo (rotação de gestão de chaves).
Como atualizar o recetor:
- Rode na interface de admin e copie o novo segredo.
- Atualize o segredo no armazenamento de segredos do seu recetor (variável de ambiente, gestor de segredos, etc.).
- Implemente o recetor atualizado.
- Confirme que a entrega seguinte é bem-sucedida no separador Entregas.
Se perdeu o novo segredo antes de o guardar:
Rode de novo. Cada rotação gera um novo segredo aleatório. O texto simples anterior não é recuperável — apenas é guardado no servidor uma forma encriptada com AEAD.