Salta ai contenuti

Webhook

I webhook permettono ai tuoi sistemi di ricevere notifiche HTTP POST in tempo reale quando si verificano eventi di coda — un biglietto si mette in coda, viene chiamato, completato, e così via.

  1. Apri admin.jonot.io/settings/integrations.
  2. Fai clic su Aggiungi endpoint.
  3. Inserisci un URL HTTPS pubblico su cui il tuo server è in ascolto.
  4. Scegli a quali eventi iscriverti.
  5. Fai clic su Salva.

Salva il tuo segreto di firma — viene mostrato una sola volta. Per verificare che il tuo endpoint sia raggiungibile, usa il pulsante Invia richiesta di prova nella pagina di modifica dell’endpoint.

Nome eventoQuando si attiva
ticket.joinedUn cliente si mette in una coda
ticket.calledUn membro del personale chiama un biglietto allo sportello
ticket.completedUn biglietto viene segnato come completato
ticket.cancelledUn cliente o un membro del personale annulla un biglietto
ticket.skippedUn biglietto viene saltato (un rinvio richiamabile)
ticket.no_showUn biglietto chiamato o saltato viene confermato come assenza
queue.status_changedLo stato di una coda cambia (ACTIVE, PAUSED o CLOSED)

Ogni consegna è una richiesta HTTP POST con:

Content-Type: application/json
X-Jonot-Event: ticket.called
X-Jonot-Delivery-Id: <uuid>
X-Jonot-Timestamp: 2024-06-01T12:00:00.000Z
X-Jonot-Signature: v1,<base64-hmac-sha256>
X-Jonot-Payload-Version: v1

Il corpo predefinito è un involucro JSON. La forma del campo payload varia in base all’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"
}
}

Jonot firma ogni consegna con HMAC-SHA256 sulla stringa:

<deliveryId>.<timestamp>.<body>

Per verificare in 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);
}

Puoi sostituire il corpo JSON predefinito con un modello personalizzato. I modelli usano una sintassi Mustache semplificata:

  • {{ path.to.value }} — sostituito ed escapato in base al tipo di contenuto: escapato come stringa JSON per application/json, codificato percentuale per application/x-www-form-urlencoded, non modificato per text/plain
  • {{{ path.to.value }}} — sostituito senza modifiche (nessun escaping)

Modello di esempio per application/json (iscritto a ticket.joined):

{
"type": "{{ event }}",
"ticketId": "{{ payload.ticket.id }}",
"queueName": "{{ payload.queue.name }}"
}

Il campo del modello di payload è un editor di codice completo con:

  • Evidenziazione della sintassi e corrispondenza delle parentesi per i modelli JSON.
  • Tavolozza delle variabili — una riga sempre visibile di pulsanti di inserimento, uno per ogni percorso di variabile disponibile per gli eventi selezionati. Fai clic su un pulsante per inserire un token {{ path }} alla posizione del cursore.
  • Validazione dal vivo — mentre digiti, l’editor controlla sia la struttura JSON (quando il tipo di contenuto è application/json e non sono presenti tag {{{ }}} non modificati) sia i percorsi delle variabili. Le diagnosi appaiono come indicatori in linea nell’editor e come banner di riepilogo sotto di esso:
    • Errore — un percorso sconosciuto in tutti gli eventi selezionati.
    • Avviso — un percorso che esiste solo per alcuni degli eventi selezionati (sarà vuoto per le consegne degli altri eventi).

La validazione viene eseguita anche al momento del salvataggio sul server — il feedback dell’editor rispecchia esattamente le regole del server.

Le consegne fallite (non-2xx o errore di connessione) vengono ritentate fino a 3 volte con backoff esponenziale da Cloudflare Queues. Dopo 3 fallimenti, la consegna finisce nella coda dead-letter e il contatore fallimenti consecutivi dell’endpoint si incrementa.

Una volta che un endpoint accumula 20 fallimenti consecutivi, viene disattivato automaticamente. Riattivalo dalla pagina di modifica dell’endpoint; il contatore si azzera.

La scheda Consegne di ciascun endpoint mostra i tentativi di consegna degli ultimi 30 giorni: tipo di evento, stato HTTP, numero di tentativi e timestamp. Usa il pulsante Carica altro per scorrere i record più vecchi.

LimiteValore
Endpoint per organizzazione5
Header personalizzati per endpoint10
Lunghezza del valore dell’header1.024 byte
Dimensione del modello di payload16 KB
Timeout di consegna10 s
Conservazione della cronologia consegne30 giorni
Frequenza massima di consegna per organizzazione120 / 60 s
  1. Apri la pagina di modifica dell’endpoint.
  2. Fai clic su Ruota il segreto di firma.
  3. Conferma la rotazione nella finestra di dialogo.
  4. Copia immediatamente il nuovo segreto — viene mostrato una sola volta e non può essere recuperato. Se chiudi la finestra senza salvarlo, dovrai ruotare di nuovo per ottenere un nuovo valore in chiaro.
  5. Aggiorna il tuo server per verificare le firme con il nuovo segreto.

Non c’è una finestra di sovrapposizione. Il vecchio segreto smette di verificare non appena confermi la rotazione. Pianifica di distribuire il nuovo segreto al tuo ricevitore immediatamente dopo la rotazione.

L’interfaccia mostra Segreto attivo: ····XXXX (gli ultimi quattro caratteri) accanto al pulsante Ruota così puoi verificare quale segreto è attualmente in vigore — utile per confermare che il tuo ricevitore e il server siano sincronizzati dopo una rotazione.

Quando ruotare:

  • Sospetto di compromissione: il segreto è apparso in un file di log, è stato condiviso con un dipendente in uscita o è stato catturato in una registrazione dello schermo.
  • Igiene di routine: la rotazione periodica limita il raggio d’azione di un’esposizione non rilevata.
  • Dopo qualsiasi modifica a quali servizi possono leggere il segreto (rotazione della gestione delle chiavi).

Come aggiornare il ricevitore:

  1. Ruota nell’interfaccia di Admin e copia il nuovo segreto.
  2. Aggiorna il segreto nell’archivio segreti del tuo ricevitore (variabile d’ambiente, gestore di segreti, ecc.).
  3. Distribuisci il ricevitore aggiornato.
  4. Conferma nella scheda Consegne che la consegna successiva ha successo.

Se hai perso il nuovo segreto prima di salvarlo:

Ruota di nuovo. Ogni rotazione genera un nuovo segreto casuale. Il valore in chiaro precedente non è recuperabile — lato server viene memorizzata solo una forma crittografata AEAD.