yuno.chat

Recevoir les webhooks

Recevez une requête HTTPS dès qu’un événement se produit : demande d’intervention d’une personne, création d’un ticket ou fin d’indexation. Disponible avec Growth et Pro.

Configurer un point de terminaison

Ajoutez un point de terminaison dans Paramètres → Développeurs → Webhooks, ou via l’API des points de terminaison webhook. Choisissez les événements souhaités et copiez le secret de signature (whsec_…).

Votre point de terminaison doit :

  • accepter des requêtes POST avec un corps JSON via HTTPS ;
  • répondre avec un statut 2xx dans un délai de 10 secondes ;
  • vérifier la signature avant d’exploiter les données reçues.

Effectuez les opérations longues, comme l’envoi d’e-mails ou les appels à d’autres API, après avoir répondu, par exemple à l’aide d’une file d’attente.

Données de l’événement

Tous les événements partagent la même structure. data.object contient l’objet complet tel qu’il était au moment de l’événement, au même format que celui renvoyé par l’API REST.

ticket.created
{
  "id": "evt_9fK2qLm0",
  "object": "event",
  "type": "ticket.created",
  "created_at": "2026-10-03T09:15:31Z",
  "livemode": true,
  "data": {
    "object": {
      "id": "tkt_7Hc1vQ",
      "object": "ticket",
      "number": 4821,
      "subject": "Tasse ébréchée dans la commande n° 1187",
      "description": "L’une des deux tasses est arrivée avec un éclat sur le bord.",
      "status": "open",
      "priority": "high",
      "contact_id": "ctc_4mR8tYw2",
      "conversation_id": "conv_8HqL2v9K",
      "assignee_id": null,
      "tags": [
        "damaged",
        "shipping"
      ],
      "metadata": {
        "order_id": "1187"
      },
      "created_at": "2026-10-03T09:15:30Z",
      "updated_at": "2026-10-03T09:15:30Z",
      "solved_at": null
    }
  }
}

Types d’événements

Événementdata.objectDéclenchement
conversation.createdconversationUn visiteur a envoyé son premier message.
conversation.handed_offconversationUn visiteur a demandé à parler à une personne, ou vous avez appelé le point de terminaison de transfert.
conversation.closedconversationUne conversation a été fermée.
message.createdmessageTout nouveau message, quel qu’en soit l’auteur.
ticket.createdticketUn ticket a été créé depuis le widget ou l’API.
ticket.updatedticketLe statut, la priorité, l’attribution ou les étiquettes d’un ticket ont changé.
ticket.solvedticketUn ticket a été marqué comme résolu.
contact.createdcontactUn nouveau contact a été créé.
knowledge_source.readyknowledge_sourceL’indexation d’une source de connaissances est terminée.
knowledge_source.failedknowledge_sourceL’indexation a échoué. Vérifiez l’URL de la source.

Abonnez-vous à * pour recevoir tous les événements, y compris les types ajoutés à l’avenir. Un événement ping est envoyé lorsque vous utilisez le bouton ou le point de terminaison de test.

Vérifier les signatures

Chaque requête contient un en-tête Yuno-Signature :

En-tête
Yuno-Signature: t=1791025531,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

t est un horodatage Unix et v1 est le HMAC-SHA256 de la chaîne {t}.{raw request body}, calculé avec votre secret de signature. Pour vérifier :

  1. Lisez le corps brut sous forme d’octets avant toute analyse JSON.
  2. Calculez le HMAC de t + "." + body et comparez-le à v1 en temps constant.
  3. Rejetez la requête si t date de plus de cinq minutes, afin d’éviter les attaques par rejeu.
import crypto from "node:crypto"
import express from "express"

const app = express()

app.post("/hooks/yuno", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("Yuno-Signature") ?? ""
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")))

  const expected = crypto
    .createHmac("sha256", process.env.YUNO_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`)
    .digest("hex")

  const valid =
    v1 && v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300

  if (!valid || !fresh) return res.status(400).send("Signature invalide")

  const event = JSON.parse(req.body)
  res.sendStatus(200)

  if (event.type === "conversation.handed_off") {
    notifyOnCall(event.data.object)
  }
})

Nouvelles tentatives

Si votre point de terminaison ne répond pas avec un statut 2xx à temps, nous réessayons en espaçant les tentatives : après 1 minute, 5 minutes, 30 minutes, 2 heures, puis toutes les 6 heures pendant 3 jours au maximum. Vous pouvez consulter chaque tentative et renvoyer des événements dans le tableau de bord.

Un point de terminaison en échec pendant 3 jours est désactivé et vous recevez un e-mail. Réactivez-le dans le tableau de bord ou avec PATCH /webhook-endpoints/{id}.

Bonnes pratiques

  • Gérez les doublons. Un même événement peut arriver plusieurs fois. Conservez les valeurs id déjà traitées et ignorez les événements correspondants.
  • Ne vous fiez pas à l’ordre de réception. Un événement ticket.updated peut arriver avant ticket.created. Comparez updated_at, ou récupérez la dernière version depuis l’API.
  • Vérifiez livemode. Les événements du mode test ont livemode: false.
Les requêtes webhook proviennent d’une liste fixe d’adresses IP, disponible dans le tableau de bord si vous devez les autoriser dans un pare-feu. La vérification de la signature reste obligatoire.