Aller au contenu

Webhooks

Les webhooks envoient une requête HTTP POST à votre système lorsqu’un événement se produit dans une file. Un ticket peut, par exemple, rejoindre la file, être appelé ou être terminé.

  1. Ouvrez admin.jonot.io/settings/integrations.
  2. Cliquez sur Ajouter un point de terminaison.
  3. Saisissez une URL HTTPS publique qui accepte les requêtes sur votre serveur.
  4. Choisissez les événements auxquels vous abonner.
  5. Cliquez sur Enregistrer.

Copiez et conservez le secret de signature lorsqu’il apparaît. Jonot ne l’affiche qu’une seule fois. Pour vérifier que Jonot peut atteindre votre point de terminaison, utilisez Envoyer une requête de test sur sa page de modification.

Nom de l’événementQuand il se déclenche
ticket.joinedUn client rejoint une file d’attente
ticket.calledUn membre du personnel appelle un ticket au guichet de service
ticket.completedUn ticket est marqué terminé
ticket.cancelledUn client ou un membre du personnel annule un ticket
ticket.skippedUn ticket est passé (un report rappelable)
ticket.no_showUn ticket appelé ou passé est confirmé comme une absence
queue.status_changedLe statut d’une file d’attente change (ACTIVE, PAUSED, ou CLOSED)

Chaque livraison est une requête HTTP POST avec :

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

Le corps par défaut est un objet JSON. Le contenu du champ payload dépend de l’événement :

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 signe chaque livraison avec HMAC-SHA256 sur la chaîne :

<deliveryId>.<timestamp>.<body>

Pour vérifier en Node.js (≥18) :

import { createHmac, timingSafeEqual } from "node:crypto";
/**
* Retourne true lorsque l'en-tête de signature est valide et que
* l'horodatage est à moins de 5 minutes de maintenant. Lève une exception
* pour une entrée malformée.
*/
function verifySignature(secret, deliveryId, timestamp, body, header) {
// Protection contre le rejeu : rejette les livraisons de plus de 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 empêche les attaques par oracle de synchronisation.
// Les tampons doivent être de la même longueur — si les longueurs
// diffèrent, la signature est invalide, mais on compare quand même une
// valeur factice pour garder un temps constant.
const expectedBuf = Buffer.from(expected);
const headerBuf = Buffer.from(header);
if (expectedBuf.length !== headerBuf.length) return false;
return timingSafeEqual(expectedBuf, headerBuf);
}

Vous pouvez remplacer le corps JSON par défaut par un modèle personnalisé. Les modèles utilisent la syntaxe Mustache allégée suivante :

  • {{ path.to.value }} — Jonot insère la valeur et l’échappe selon le type de contenu : échappement de chaîne JSON pour application/json, encodage en pourcentage pour application/x-www-form-urlencoded et aucun échappement pour text/plain.
  • {{{ path.to.value }}} — Jonot insère la valeur sans l’échapper.

Exemple de modèle pour application/json (abonné à ticket.joined) :

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

Le champ de modèle de charge utile est un éditeur de code complet avec :

  • Coloration syntaxique et appariement des accolades pour les modèles JSON.
  • Liste des variables — une rangée de boutons pour les chemins disponibles dans les événements sélectionnés. Cliquez sur un bouton pour insérer un jeton {{ path }} au curseur.
  • Validation en direct — pendant la saisie, l’éditeur vérifie les chemins des variables. Il vérifie aussi la structure JSON lorsque le type de contenu est application/json et que le modèle ne contient aucune balise brute {{{ }}}. Il marque les problèmes dans le code et les répertorie en dessous :
    • Erreur — un chemin inconnu dans tous vos événements sélectionnés.
    • Avertissement — un chemin qui n’existe que pour certains de vos événements sélectionnés (il sera vide pour les livraisons des autres événements).

Le serveur applique les mêmes règles de validation lors de l’enregistrement.

Cloudflare Queues réessaie jusqu’à 3 fois une livraison échouée, avec un délai plus long avant chaque tentative. Une livraison échoue si le point de terminaison renvoie un statut hors de la plage 2xx ou rencontre une erreur de connexion. Si la première tentative et les 3 nouvelles tentatives échouent, la livraison passe dans la file des messages morts et le compteur d’échecs consécutifs du point de terminaison augmente.

Jonot désactive automatiquement un point de terminaison après 20 échecs consécutifs. Vous pouvez le réactiver depuis sa page de modification. Le compteur revient alors à zéro.

L’onglet Livraisons de chaque point de terminaison affiche les tentatives des 30 derniers jours. Chaque entrée comprend le type d’événement, le statut HTTP, le nombre de tentatives et l’horodatage. Utilisez Charger plus pour voir les entrées plus anciennes de cette période.

LimiteValeur
Points de terminaison par organisation5
En-têtes personnalisés par point de terminaison10
Longueur de valeur d’en-tête1 024 octets
Taille du modèle de charge utile16 Ko
Délai d’expiration de livraison10 s
Rétention de l’historique de livraison30 jours
Taux de livraison max. par organisation120 / 60 s
  1. Ouvrez la page de modification du point de terminaison.
  2. Cliquez sur Renouveler le secret de signature.
  3. Confirmez la rotation dans la boîte de dialogue.
  4. Copiez immédiatement le nouveau secret. Jonot ne l’affiche qu’une fois et ne peut pas le récupérer. Si vous fermez la boîte de dialogue sans le conserver, effectuez une nouvelle rotation pour obtenir une autre valeur.
  5. Mettez à jour votre serveur pour vérifier les signatures avec le nouveau secret.

L’ancien et le nouveau secrets ne fonctionnent pas en même temps. L’ancien cesse de vérifier les requêtes dès que vous confirmez la rotation. Mettez à jour le système qui reçoit les webhooks immédiatement après cette opération.

L’interface affiche Secret actif : ····XXXX (les quatre derniers caractères) à côté du bouton Rotation afin que vous puissiez vérifier quel secret est actuellement en vigueur — utile pour confirmer que votre récepteur et le serveur sont synchronisés après une rotation.

Quand faire pivoter :

  • Exposition suspectée : le secret est apparu dans un journal, a été partagé avec un employé qui quitte l’organisation ou a été capturé dans un enregistrement d’écran.
  • Rotation régulière : modifier le secret périodiquement réduit la durée pendant laquelle un secret exposé peut être utilisé.
  • Changement d’accès : effectuez une rotation après avoir modifié les services autorisés à lire le secret.

Comment mettre à jour le récepteur :

  1. Faites pivoter dans l’interface Admin et copiez le nouveau secret.
  2. Mettez à jour le secret dans le magasin de secrets de votre récepteur (variable d’environnement, gestionnaire de secrets, etc.).
  3. Déployez le récepteur mis à jour.
  4. Confirmez que la prochaine livraison réussit dans l’onglet Livraisons.

Si vous avez perdu le nouveau secret avant de l’enregistrer :

Faites pivoter à nouveau. Chaque rotation génère un nouveau secret aléatoire. Le texte en clair précédent n’est pas récupérable — seule une forme chiffrée AEAD est stockée côté serveur.