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.
Pierwsze kroki
Dział zatytułowany „Pierwsze kroki”- Otwórz admin.jonot.io/settings/integrations.
- Kliknij Dodaj punkt końcowy.
- Wprowadź publiczny adres URL HTTPS, na którym nasłuchuje Twój serwer.
- Wybierz zdarzenia, które chcesz zasubskrybować.
- 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.
Typy zdarzeń
Dział zatytułowany „Typy zdarzeń”| Nazwa zdarzenia | Kiedy jest wywoływane |
|---|---|
ticket.joined | Klient dołącza do kolejki |
ticket.called | Członek personelu wywołuje bilet do stanowiska obsługi |
ticket.completed | Bilet zostaje oznaczony jako zakończony |
ticket.cancelled | Klient lub członek personelu anuluje bilet |
ticket.skipped | Bilet zostaje pominięty (odroczenie z możliwością przywołania) |
ticket.no_show | Wywołany lub pominięty bilet zostaje potwierdzony jako nieobecność |
queue.status_changed | Zmienia się status kolejki (ACTIVE, PAUSED lub CLOSED) |
Format ładunku
Dział zatytułowany „Format ładunku”Każda wysyłka to żądanie HTTP POST z:
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: v1Domyś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" }}Weryfikacja sygnatury
Dział zatytułowany „Weryfikacja sygnatury”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);}Szablony ładunku
Dział zatytułowany „Szablony ładunku”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 dlaapplication/json, zakodowane procentowo dlaapplication/x-www-form-urlencoded, bez zmian dlatext/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 }}"}Edytor szablonu
Dział zatytułowany „Edytor szablonu”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/jsoni 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.
Ponawianie wysyłek
Dział zatytułowany „Ponawianie wysyłek”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.
Historia wysyłek
Dział zatytułowany „Historia wysyłek”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.
| Limit | Wartość |
|---|---|
| Punkty końcowe na organizację | 5 |
| Niestandardowe nagłówki na punkt końcowy | 10 |
| Długość wartości nagłówka | 1024 bajty |
| Rozmiar szablonu ładunku | 16 KB |
| Limit czasu wysyłki | 10 s |
| Retencja historii wysyłek | 30 dni |
| Maksymalna szybkość wysyłki na organizację | 120 / 60 s |
Wymiana klucza podpisującego
Dział zatytułowany „Wymiana klucza podpisującego”- Otwórz stronę edycji punktu końcowego.
- Kliknij Wymień klucz podpisujący.
- Potwierdź wymianę w oknie dialogowym.
- 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.
- 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.
Higiena klucza podpisującego
Dział zatytułowany „Higiena klucza podpisującego”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:
- Wymień klucz w interfejsie administracyjnym i skopiuj nowy klucz.
- Zaktualizuj klucz w magazynie sekretów Twojego odbiornika (zmienna środowiskowa, menedżer sekretów itp.).
- Wdróż zaktualizowany odbiornik.
- 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.