Zum Inhalt springen

Webhooks

Webhooks senden eine HTTP-POST-Anfrage an Ihr System, wenn ein Ereignis in einer Warteschlange eintritt. Dazu gehören der Beitritt, Aufruf und Abschluss eines Tickets.

  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.

Kopieren und speichern Sie das Signaturgeheimnis, wenn es erscheint. Jonot zeigt es nur einmal an. Mit Testanfrage senden auf der Bearbeitungsseite prüfen Sie, ob Jonot den Endpunkt erreicht.

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.

Cloudflare Queues wiederholt eine fehlgeschlagene Zustellung bis zu 3-mal und wartet vor jedem Versuch länger. Eine Zustellung schlägt fehl, wenn der Endpunkt keinen 2xx-Status zurückgibt oder ein Verbindungsfehler auftritt. Schlagen der erste Versuch und alle 3 Wiederholungen fehl, verschiebt Jonot die Zustellung in die Dead-Letter-Queue und erhöht den Zähler aufeinanderfolgende Fehlschläge.

Nach 20 aufeinanderfolgenden Fehlschlägen deaktiviert Jonot den Endpunkt automatisch. Sie können ihn auf der Bearbeitungsseite wieder aktivieren. Dadurch wird der Zähler auf null gesetzt.

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.

Das alte und das neue Geheimnis funktionieren nicht gleichzeitig. Sobald Sie die Rotation bestätigen, ist das alte Geheimnis ungültig. Aktualisieren Sie das empfangende System daher direkt nach der Rotation.

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:

  • Vermutete Offenlegung: Das Geheimnis erschien in einer Protokolldatei, wurde mit einem ausscheidenden Mitarbeiter geteilt oder in einer Bildschirmaufzeichnung erfasst.
  • Regelmäßiger Wechsel: Ein regelmäßiger Wechsel begrenzt, wie lange ein offengelegtes Geheimnis verwendet werden kann.
  • Zugriffsänderungen: Wechseln Sie das Geheimnis, nachdem Sie geändert haben, welche Dienste darauf zugreifen können.

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.