Ga naar inhoud

Webhooks

Met webhooks kunnen je systemen realtime HTTP POST-meldingen ontvangen wanneer wachtrijgebeurtenissen plaatsvinden — een ticket sluit aan, wordt opgeroepen, wordt afgerond, enzovoort.

  1. Open admin.jonot.io/settings/integrations.
  2. Klik op Endpoint toevoegen.
  3. Voer een openbare HTTPS-URL in waar je server naar luistert.
  4. Kies op welke gebeurtenissen je je wilt abonneren.
  5. Klik op Opslaan.

Bewaar je ondertekeningsgeheim — dit wordt slechts één keer getoond. Om te controleren of je eindpunt bereikbaar is, gebruik je de knop Testverzoek versturen op de bewerkingspagina van het eindpunt.

Naam van de gebeurtenisWanneer deze afgaat
ticket.joinedEen klant sluit aan bij een wachtrij
ticket.calledEen personeelslid roept een ticket op naar de servicebalie
ticket.completedEen ticket wordt gemarkeerd als afgerond
ticket.cancelledEen klant of personeelslid annuleert een ticket
ticket.skippedEen ticket wordt overgeslagen (een terugroepbaar uitstel)
ticket.no_showEen opgeroepen of overgeslagen ticket wordt bevestigd als no-show
queue.status_changedDe status van een wachtrij verandert (ACTIVE, PAUSED of CLOSED)

Elke aflevering is een HTTP POST met:

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

De standaardinhoud is een JSON-envelop. De vorm van het payload-veld varieert per gebeurtenis:

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 ondertekent elke aflevering met HMAC-SHA256 over de string:

<deliveryId>.<timestamp>.<body>

Verifiëren in Node.js (≥18):

import { createHmac, timingSafeEqual } from "node:crypto";
/**
* Geeft true terug wanneer de signature-header geldig is en het tijdstempel
* binnen 5 minuten van nu ligt. Gooit een fout bij misvormde invoer.
*/
function verifySignature(secret, deliveryId, timestamp, body, header) {
// Bescherming tegen replay-aanvallen: weiger afleveringen ouder dan 5 minuten.
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 voorkomt timing-oracle-aanvallen.
// Buffers moeten dezelfde lengte hebben — als de lengtes verschillen is de
// handtekening ongeldig, maar we vergelijken toch een dummy-waarde om
// constante tijd te behouden.
const expectedBuf = Buffer.from(expected);
const headerBuf = Buffer.from(header);
if (expectedBuf.length !== headerBuf.length) return false;
return timingSafeEqual(expectedBuf, headerBuf);
}

Je kunt de standaard JSON-inhoud vervangen door een aangepast sjabloon. Sjablonen gebruiken Mustache-lite-syntaxis:

  • {{ path.to.value }} — vervangen en geëscaped voor het content-type: JSON-string-geëscaped voor application/json, percent-encoded voor application/x-www-form-urlencoded, ruw voor text/plain
  • {{{ path.to.value }}} — ruw vervangen (geen escaping)

Voorbeeldsjabloon voor application/json (geabonneerd op ticket.joined):

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

Het veld voor het payload-sjabloon is een volledige code-editor met:

  • Syntaxismarkering en haakjeskoppeling voor JSON-sjablonen.
  • Variabelenpalet — een altijd zichtbare rij invoegknoppen, één per beschikbaar variabelenpad voor je geselecteerde gebeurtenissen. Klik op een knop om een {{ path }}-token op de cursorpositie in te voegen.
  • Live validatie — terwijl je typt, controleert de editor zowel de JSON-structuur (wanneer het content-type application/json is en er geen ruwe {{{ }}}-tags aanwezig zijn) als de variabelenpaden. Diagnostiek verschijnt als inline markeringen in de editor en als een samenvattingsbanner eronder:
    • Fout — een pad dat onbekend is in al je geselecteerde gebeurtenissen.
    • Waarschuwing — een pad dat alleen bestaat voor sommige van je geselecteerde gebeurtenissen (het zal leeg zijn voor afleveringen van de andere gebeurtenissen).

Validatie draait ook bij het opslaan op de server — de feedback van de editor weerspiegelt exact dezelfde regels als de server.

Mislukte afleveringen (niet-2xx of verbindingsfout) worden door Cloudflare Queues tot 3 keer opnieuw geprobeerd met exponentiële back-off. Na 3 mislukkingen komt de aflevering in de dead-letter-wachtrij terecht en verhoogt de teller opeenvolgende mislukkingen van het eindpunt.

Zodra een eindpunt 20 opeenvolgende mislukkingen heeft opgebouwd, wordt het automatisch uitgeschakeld. Schakel het weer in vanaf de bewerkingspagina van het eindpunt; de teller wordt teruggezet naar nul.

Het tabblad Afleveringen op elk eindpunt toont de afleverpogingen van de afgelopen 30 dagen: gebeurtenistype, HTTP-status, aantal pogingen en tijdstempel. Gebruik de knop Meer laden om door oudere records te bladeren.

LimietWaarde
Eindpunten per organisatie5
Aangepaste headers per eindpunt10
Lengte van headerwaarde1.024 bytes
Grootte van payload-sjabloon16 KB
Time-out voor aflevering10 s
Bewaartermijn afleveringsgeschiedenis30 dagen
Max. afleverfrequentie per organisatie120 / 60 s
  1. Open de bewerkingspagina van het eindpunt.
  2. Klik op Ondertekeningsgeheim roteren.
  3. Bevestig de rotatie in het dialoogvenster.
  4. Kopieer het nieuwe geheim onmiddellijk — het wordt slechts één keer getoond en kan niet worden hersteld. Als je het dialoogvenster sluit zonder het op te slaan, moet je opnieuw roteren om een nieuwe leestekst te verkrijgen.
  5. Werk je server bij om handtekeningen te verifiëren met het nieuwe geheim.

Er is geen overlapvenster. Het oude geheim stopt met verifiëren zodra je de rotatie bevestigt. Plan om het nieuwe geheim onmiddellijk na het roteren naar je ontvanger te deployen.

De interface toont Actief geheim: ····XXXX (de laatste vier tekens) naast de knop Roteren, zodat je kunt verifiëren welk geheim momenteel actief is — handig om te bevestigen dat je ontvanger en de server na een rotatie synchroon lopen.

Wanneer roteren:

  • Vermoede compromittering: het geheim verscheen in een logbestand, werd gedeeld met een vertrekkende medewerker, of werd vastgelegd in een schermopname.
  • Routinehygiëne: periodiek roteren beperkt de impactradius van een onopgemerkte blootstelling.
  • Na elke wijziging in welke services het geheim kunnen lezen (rotatie van sleutelbeheer).

Hoe je de ontvanger bijwerkt:

  1. Roteer in de admin-interface en kopieer het nieuwe geheim.
  2. Werk het geheim bij in de geheimenopslag van je ontvanger (omgevingsvariabele, secrets manager, enz.).
  3. Deploy de bijgewerkte ontvanger.
  4. Bevestig dat de volgende aflevering slaagt in het tabblad Afleveringen.

Als je het nieuwe geheim bent kwijtgeraakt voordat je het hebt opgeslagen:

Roteer opnieuw. Elke rotatie genereert een nieuw willekeurig geheim. De vorige leestekst kan niet worden hersteld — server-side wordt alleen een AEAD-versleutelde vorm opgeslagen.