Przejdź do głównej zawartości

Webhooki

Webhooki pozwalają Twoim systemom otrzymywać powiadomienia HTTP POST w czasie rzeczywistym, gdy występują zdarzenia kolejki — bilet dołącza, zostaje wywołany, kończy się i tak dalej.

  1. Otwórz admin.jonot.io/settings/integrations.
  2. Kliknij Dodaj punkt końcowy.
  3. Wprowadź publiczny adres URL HTTPS, na którym nasłuchuje Twój serwer.
  4. Wybierz zdarzenia, które chcesz zasubskrybować.
  5. Kliknij Zapisz punkt końcowy.

Zapisz swój klucz podpisujący — jest wyświetlany tylko raz. Aby sprawdzić, czy Twój punkt końcowy jest osiągalny, użyj przycisku Wyślij żądanie testowe na stronie edycji punktu końcowego.

Nazwa zdarzeniaKiedy jest wywoływane
ticket.joinedKlient dołącza do kolejki
ticket.calledCzłonek personelu wywołuje bilet do stanowiska obsługi
ticket.completedBilet zostaje oznaczony jako zakończony
ticket.cancelledKlient lub członek personelu anuluje bilet
ticket.skippedBilet zostaje pominięty (odroczenie z możliwością przywołania)
ticket.no_showWywołany lub pominięty bilet zostaje potwierdzony jako nieobecność
queue.status_changedZmienia się status kolejki (ACTIVE, PAUSED lub CLOSED)

Każda wysyłka to żądanie HTTP POST z:

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

Domyślne ciało żądania to koperta JSON. Kształt pola payload zależy od zdarzenia:

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 podpisuje każdą wysyłkę za pomocą HMAC-SHA256 nad ciągiem:

<deliveryId>.<timestamp>.<body>

Aby zweryfikować w 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);
}

Możesz zastąpić domyślne ciało JSON niestandardowym szablonem. Szablony używają uproszczonej składni Mustache:

  • {{ path.to.value }} — podstawiane i escapowane odpowiednio do typu treści: jako ciąg JSON dla application/json, zakodowane procentowo dla application/x-www-form-urlencoded, bez zmian dla text/plain
  • {{{ path.to.value }}} — podstawiane bez zmian (bez escapowania)

Przykładowy szablon dla application/json (zasubskrybowane ticket.joined):

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

Pole szablonu ładunku to pełny edytor kodu z:

  • Podświetlaniem składni i dopasowywaniem nawiasów dla szablonów JSON.
  • Paletą zmiennych — zawsze widocznym rzędem przycisków wstawiania, po jednym dla każdej dostępnej ścieżki zmiennej dla wybranych zdarzeń. Kliknięcie przycisku wstawia token {{ path }} w miejscu kursora.
  • Walidacją na żywo — podczas pisania edytor sprawdza zarówno strukturę JSON (gdy typ treści to application/json i nie występują surowe znaczniki {{{ }}}), jak i ścieżki zmiennych. Diagnostyka pojawia się jako znaczniki w tekście edytora oraz jako podsumowujący baner poniżej:
    • Błąd — ścieżka nieznana we wszystkich wybranych zdarzeniach.
    • Ostrzeżenie — ścieżka istniejąca tylko dla części wybranych zdarzeń (dla wysyłek pozostałych zdarzeń będzie pusta).

Walidacja jest też uruchamiana przy zapisie na serwerze — informacje zwrotne edytora dokładnie odzwierciedlają reguły serwera.

Nieudane wysyłki (odpowiedź inna niż 2xx lub błąd połączenia) są ponawiane przez Cloudflare Queues do 3 razy z wykładniczym opóźnieniem. Po 3 niepowodzeniach wysyłka trafia do kolejki dead-letter, a licznik kolejnych niepowodzeń punktu końcowego zwiększa się.

Gdy punkt końcowy zgromadzi 20 kolejnych niepowodzeń, zostaje automatycznie wyłączony. Włącz go ponownie na stronie edycji punktu końcowego; licznik resetuje się do zera.

Zakładka Wysyłki każdego punktu końcowego pokazuje próby wysyłki z ostatnich 30 dni: typ zdarzenia, status HTTP, liczbę prób i znacznik czasu. Użyj przycisku Załaduj więcej, aby przeglądać starsze wpisy.

LimitWartość
Punkty końcowe na organizację5
Niestandardowe nagłówki na punkt końcowy10
Długość wartości nagłówka1024 bajty
Rozmiar szablonu ładunku16 KB
Limit czasu wysyłki10 s
Retencja historii wysyłek30 dni
Maksymalna szybkość wysyłki na organizację120 / 60 s
  1. Otwórz stronę edycji punktu końcowego.
  2. Kliknij Wymień klucz podpisujący.
  3. Potwierdź wymianę w oknie dialogowym.
  4. Skopiuj nowy klucz natychmiast — jest wyświetlany tylko raz i nie można go odzyskać. Jeśli zamkniesz okno bez jego zapisania, musisz wymienić go ponownie, aby otrzymać nowy tekst jawny.
  5. Zaktualizuj swój serwer, aby weryfikował sygnatury nowym kluczem.

Nie ma okresu nakładania się. Stary klucz przestaje działać, gdy tylko potwierdzisz wymianę. Zaplanuj wdrożenie nowego klucza w swoim odbiorniku natychmiast po wymianie.

Interfejs pokazuje Aktywny klucz: ····XXXX (ostatnie cztery znaki) obok przycisku wymiany, dzięki czemu można sprawdzić, który klucz jest obecnie aktywny — przydatne do potwierdzenia, że Twój odbiornik i serwer są zsynchronizowane po wymianie.

Kiedy wymieniać:

  • Podejrzenie kompromitacji: klucz pojawił się w pliku logu, został udostępniony odchodzącemu pracownikowi lub został uchwycony w nagraniu ekranu.
  • Rutynowa higiena: okresowa wymiana ogranicza zasięg skutków niewykrytego ujawnienia.
  • Po każdej zmianie tego, które usługi mają dostęp do klucza (rotacja zarządzania kluczami).

Jak zaktualizować odbiornik:

  1. Wymień klucz w interfejsie administracyjnym i skopiuj nowy klucz.
  2. Zaktualizuj klucz w magazynie sekretów Twojego odbiornika (zmienna środowiskowa, menedżer sekretów itp.).
  3. Wdróż zaktualizowany odbiornik.
  4. Potwierdź w zakładce Wysyłki, że kolejna wysyłka się powiodła.

Jeśli nowy klucz zostanie zgubiony przed jego zapisaniem:

Wymień klucz ponownie. Każda wymiana generuje nowy losowy klucz. Poprzedni tekst jawny nie jest możliwy do odzyskania — po stronie serwera przechowywana jest tylko forma zaszyfrowana AEAD.