Przejdź do głównej zawartości

Webhooki

Webhooki wysyłają żądanie HTTP POST do Twojego systemu, gdy w kolejce zachodzi zdarzenie. Zdarzeniem jest na przykład dołączenie biletu do kolejki, jego wywołanie lub zakończenie.

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

Skopiuj i zapisz klucz podpisujący, gdy się pojawi. Jonot pokazuje go tylko raz. Aby sprawdzić, czy Jonot dociera do Twojego punktu końcowego, użyj przycisku Wyślij żądanie testowe na stronie edycji punktu końcowego.

Nazwa zdarzeniaKiedy Jonot je wysyła
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 nagłówkami:

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 obiekt JSON. Zawartość 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ę algorytmem HMAC-SHA256 na ciągu:

<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);
}

Domyślne ciało JSON możesz zastąpić własnym szablonem. Szablony korzystają z uproszczonej składni Mustache:

  • {{ path.to.value }} — Jonot wstawia wartość i zabezpiecza ją odpowiednio do typu treści: jako ciąg JSON dla application/json, w kodowaniu procentowym dla application/x-www-form-urlencoded, bez zmian dla text/plain.
  • {{{ path.to.value }}} — Jonot wstawia wartość bez żadnego zabezpieczania.

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.
  • Listą zmiennych — rzędem przycisków ze ścieżkami zmiennych dostępnymi dla wybranych zdarzeń. Kliknij przycisk, aby wstawić token {{ path }} w miejscu kursora.
  • Walidacją na żywo — podczas pisania edytor sprawdza ścieżki zmiennych. Sprawdza też strukturę JSON, gdy typ treści to application/json, a szablon nie zawiera surowych znaczników {{{ }}}. Edytor zaznacza problemy w kodzie i wypisuje je 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).

Przy zapisie serwer stosuje te same reguły walidacji.

Cloudflare Queues ponawia nieudaną wysyłkę do 3 razy, za każdym razem z dłuższym opóźnieniem. Wysyłka jest nieudana, gdy punkt końcowy zwróci status spoza zakresu 2xx albo gdy wystąpi błąd połączenia. Jeśli pierwsza próba i wszystkie 3 ponowienia zawiodą, wysyłka trafia do kolejki dead-letter, a licznik kolejnych niepowodzeń punktu końcowego rośnie.

Po 20 kolejnych niepowodzeniach Jonot automatycznie wyłącza punkt końcowy. Możesz włączyć go ponownie na stronie edycji punktu końcowego. Licznik wraca wtedy do zera.

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

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. Jonot pokazuje go raz i nie potrafi go odtworzyć. Jeśli zamkniesz okno bez zapisania klucza, wymień klucz ponownie, aby otrzymać nową wartość.
  5. Zaktualizuj swój serwer, aby weryfikował podpisy nowym kluczem.

Stary i nowy klucz nigdy nie działają jednocześnie. Stary klucz przestaje weryfikować żądania w momencie, w którym potwierdzisz wymianę. Zaktualizuj system odbierający webhooki natychmiast po wymianie.

Interfejs pokazuje Aktywny klucz: ····XXXX obok przycisku wymiany. Ostatnie cztery znaki pozwalają potwierdzić, który klucz jest aktywny po wymianie.

Kiedy wymieniać:

  • Podejrzenie kompromitacji: klucz pojawił się w pliku logu, został udostępniony odchodzącemu pracownikowi lub został uchwycony w nagraniu ekranu.
  • Regularna wymiana: okresowa zmiana klucza skraca czas, przez który ujawniony klucz da się wykorzystać.
  • Zmiana dostępu: wymień klucz po zmianie tego, które usługi mogą go odczytać.

Jak zaktualizować odbiornik:

  1. Wymień klucz w interfejsie administracyjnym i skopiuj nowy klucz.
  2. Zaktualizuj klucz używany przez system odbierający. Przechowuj go w zmiennej środowiskowej, menedżerze sekretów lub innym bezpiecznym magazynie.
  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. Jonot nie odtworzy poprzedniej czytelnej wartości, ponieważ serwer przechowuje wyłącznie postać zaszyfrowaną.