Zum Inhalt springen

Webhooks

Mit Webhooks können Ihre Systeme Echtzeit-HTTP-POST-Benachrichtigungen empfangen, wenn Warteschlangenereignisse eintreten — ein Ticket tritt bei, wird aufgerufen, abgeschlossen und so weiter.

  1. Öffnen Sie admin.jonot.io/settings/integrations.
  2. Klicken Sie auf Endpunkt hinzufügen.
  3. Geben Sie eine öffentliche HTTPS-URL ein, auf der Ihr Server lauscht.
  4. Wählen Sie die Ereignisse aus, die Sie abonnieren möchten.
  5. Klicken Sie auf Endpunkt speichern.

Speichern Sie Ihr Signaturgeheimnis — es wird nur einmal angezeigt. Um zu prüfen, ob Ihr Endpunkt erreichbar ist, verwenden Sie die Schaltfläche Testanfrage senden auf der Bearbeitungsseite des Endpunkts.

EreignisnameWann es ausgelöst wird
ticket.joinedEin Kunde tritt einer Warteschlange bei
ticket.calledEin Mitarbeiter ruft ein Ticket zum Schalter auf
ticket.completedEin Ticket wird als abgeschlossen markiert
ticket.cancelledEin Kunde oder Mitarbeiter storniert ein Ticket
ticket.skippedEin Ticket wird übersprungen (ein erneut aufrufbarer Aufschub)
ticket.no_showEin aufgerufenes oder übersprungenes Ticket wird als Nichterscheinen bestätigt
queue.status_changedDer Status einer Warteschlange ändert sich (ACTIVE, PAUSED oder CLOSED)

Jede Zustellung ist ein HTTP-POST mit:

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

Der Standardkörper ist ein JSON-Umschlag. Die Form des payload-Felds variiert je nach Ereignis:

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 signiert jede Zustellung mit HMAC-SHA256 über die Zeichenkette:

<deliveryId>.<timestamp>.<body>

Überprüfung 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);
}

Sie können den Standard-JSON-Körper durch eine benutzerdefinierte Vorlage ersetzen. Vorlagen verwenden eine vereinfachte Mustache-Syntax:

  • {{ path.to.value }} — wird ersetzt und je nach Inhaltstyp escaped: als JSON-String escaped für application/json, prozentkodiert für application/x-www-form-urlencoded, unverändert für text/plain
  • {{{ path.to.value }}} — wird unverändert ersetzt (kein Escaping)

Beispielvorlage für application/json (abonniert für ticket.joined):

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

Das Feld für die Payload-Vorlage ist ein vollständiger Code-Editor mit:

  • Syntaxhervorhebung und Klammerabgleich für JSON-Vorlagen.
  • Variablenpalette — eine stets sichtbare Zeile mit Einfügeschaltflächen, eine pro verfügbarem Variablenpfad für Ihre ausgewählten Ereignisse. Klicken Sie auf eine Schaltfläche, um ein {{ path }}-Token an der Cursorposition einzufügen.
  • Live-Validierung — während der Eingabe prüft der Editor sowohl die JSON-Struktur (wenn der Inhaltstyp application/json ist und keine rohen {{{ }}}-Tags vorhanden sind) als auch die Variablenpfade. Diagnosen erscheinen als Inline-Markierungen im Editor und als zusammenfassendes Banner darunter:
    • Fehler — ein Pfad, der in allen Ihren ausgewählten Ereignissen unbekannt ist.
    • Warnung — ein Pfad, der nur für einige Ihrer ausgewählten Ereignisse existiert (er ist bei Zustellungen der anderen Ereignisse leer).

Die Validierung läuft auch beim Speichern auf dem Server — die Editor-Rückmeldung entspricht exakt den Serverregeln.

Fehlgeschlagene Zustellungen (kein 2xx oder Verbindungsfehler) werden von Cloudflare Queues mit exponentiellem Backoff bis zu 3-mal erneut versucht. Nach 3 Fehlschlägen landet die Zustellung in der Dead-Letter-Queue, und der Zähler aufeinanderfolgende Fehlschläge des Endpunkts erhöht sich.

Sobald ein Endpunkt 20 aufeinanderfolgende Fehlschläge ansammelt, wird er automatisch deaktiviert. Aktivieren Sie ihn über die Bearbeitungsseite des Endpunkts erneut; der Zähler wird auf null zurückgesetzt.

Der Tab Zustellungen jedes Endpunkts zeigt die Zustellversuche der letzten 30 Tage: Ereignistyp, HTTP-Status, Versuchsanzahl und Zeitstempel. Verwenden Sie die Schaltfläche Mehr laden, um ältere Einträge zu durchblättern.

LimitWert
Endpunkte pro Organisation5
Benutzerdefinierte Header pro Endpunkt10
Länge des Header-Werts1.024 Bytes
Größe der Payload-Vorlage16 KB
Zustellungs-Timeout10 s
Aufbewahrungsdauer des Zustellungsverlaufs30 Tage
Maximale Zustellrate pro Organisation120 / 60 s
  1. Öffnen Sie die Bearbeitungsseite des Endpunkts.
  2. Klicken Sie auf Signaturgeheimnis rotieren.
  3. Bestätigen Sie die Rotation im Dialog.
  4. Kopieren Sie das neue Geheimnis sofort — es wird nur einmal angezeigt und kann nicht wiederhergestellt werden. Schließen Sie den Dialog, ohne es zu speichern, müssen Sie erneut rotieren, um einen neuen Klartextwert zu erhalten.
  5. Aktualisieren Sie Ihren Server, damit er Signaturen mit dem neuen Geheimnis überprüft.

Es gibt kein Überlappungsfenster. Das alte Geheimnis verliert seine Gültigkeit, sobald Sie die Rotation bestätigen. Planen Sie, das neue Geheimnis unmittelbar nach der Rotation auf Ihrem Empfänger bereitzustellen.

Die Oberfläche zeigt Aktives Geheimnis: ····XXXX (die letzten vier Zeichen) neben der Rotieren-Schaltfläche, damit Sie prüfen können, welches Geheimnis derzeit gültig ist — nützlich, um nach einer Rotation zu bestätigen, dass Ihr Empfänger und der Server synchron sind.

Wann rotieren:

  • Vermuteter Kompromittierung: Das Geheimnis erschien in einer Protokolldatei, wurde mit einem ausscheidenden Mitarbeiter geteilt oder in einer Bildschirmaufzeichnung erfasst.
  • Routinehygiene: regelmäßiges Rotieren begrenzt den Schaden einer unentdeckten Offenlegung.
  • Nach jeder Änderung daran, welche Dienste das Geheimnis lesen können (Rotation der Schlüsselverwaltung).

So aktualisieren Sie den Empfänger:

  1. Rotieren Sie in der Admin-Oberfläche und kopieren Sie das neue Geheimnis.
  2. Aktualisieren Sie das Geheimnis im Secret-Store Ihres Empfängers (Umgebungsvariable, Secrets-Manager usw.).
  3. Stellen Sie den aktualisierten Empfänger bereit.
  4. Bestätigen Sie im Tab Zustellungen, dass die nächste Zustellung erfolgreich ist.

Falls Sie das neue Geheimnis vor dem Speichern verloren haben:

Rotieren Sie erneut. Jede Rotation erzeugt ein neues zufälliges Geheimnis. Der vorherige Klartext ist nicht wiederherstellbar — serverseitig wird nur eine AEAD-verschlüsselte Form gespeichert.