Aller au contenu

Le webhook

Plutôt que d’interroger GET /api/message/{messageId} en boucle, laissez Assilya pousser le résultat vers votre serveur : dès qu’un message atteint un état stable, votre URL reçoit un POST signé contenant exactement la même enveloppe JSON que le polling — un seul parseur suffit.

Administrateur

Dans les paramètres de l’agent API, section « Webhook » :

  1. Activez l’interrupteur — un secret de signature (préfixe whsec_) est généré automatiquement ; copiez-le dans la configuration de votre serveur.
  2. Renseignez votre URL de rappel. Elle doit être en HTTPS — c’est une exigence stricte : sans URL https://, aucun envoi n’a lieu.
  3. Le bouton « Régénérer » du secret invalide l’ancien immédiatement — mettez votre serveur à jour d’abord.
X-Assilya-EventDéclencheur
message.answeredUne réponse a été envoyée — automatiquement ou publiée par un humain (boîte de réception ou POST …/answer).
message.pendingL’IA passe la main (confiance sous le seuil, ou action à effectuer par un humain) : des suggestions attendent une validation humaine.
message.failedLe traitement a échoué (error dans le corps).

Le corps est l’enveloppe décrite dans Le cycle de vie d’un message ; l’événement reprend simplement son champ state.

En-têteContenu
X-Assilya-Eventmessage.answered, message.pending ou message.failed.
X-Assilya-MessageL’identifiant du message entrant concerné (le champ id du corps).
X-Assilya-TimestampHorodatage Unix (secondes) — entre dans la signature.
X-Assilya-Signaturesha256=<hmac> (voir ci-dessous).
User-AgentAssilya-Webhook/1.0
Content-Typeapplication/json

La signature est un HMAC-SHA256 du texte timestamp.corps (l’horodatage, un point, puis le corps brut de la requête), calculé avec votre secret whsec_ :

signature = HMAC_SHA256(timestamp + "." + corps_brut, webhook_secret)

Deux impératifs : recalculez-la depuis le corps brut (avant tout décodage JSON — un ré-encodage changerait les octets), et comparez en temps constant.

En PHP :

$corps = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_ASSILYA_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_ASSILYA_SIGNATURE'] ?? '';
$attendue = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$corps, $webhookSecret);
if (! hash_equals($attendue, $signature)) {
http_response_code(401);
exit;
}

En Node.js (Express) :

import crypto from 'node:crypto';
app.post('/webhooks/assilya', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.header('X-Assilya-Timestamp') ?? '';
const signature = req.header('X-Assilya-Signature') ?? '';
const attendue = 'sha256=' + crypto
.createHmac('sha256', process.env.ASSILYA_WEBHOOK_SECRET)
.update(timestamp + '.' + req.body)
.digest('hex');
const valide = signature.length === attendue.length
&& crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(attendue));
if (!valide) return res.status(401).end();
res.status(200).end();
// Traitez ensuite le contenu (déjà persisté) de façon asynchrone.
});

Refusez aussi les horodatages trop anciens (quelques minutes) pour bloquer le rejeu d’une requête capturée.

  • La première tentative part immédiatement ; votre serveur dispose de 30 secondes pour répondre.
  • Toute réponse 2xx vaut accusé de réception et stoppe les relances.
  • En cas d’échec (non-2xx ou délai dépassé), l’envoi est retenté à +60 s, +5 min puis +15 min4 tentatives en tout.
  • Après le quatrième échec, la livraison est abandonnée : prévoyez le repli par polling pour ne rien perdre.
  • Accusez réception vite : persistez le payload, répondez 2xx, traitez ensuite. Un traitement lourd dans le handler vous expose au délai de 30 s — et donc à des relances en doublon.
  • Soyez idempotent : une même notification peut arriver plusieurs fois (relances) ; le champ id est stable et sert de clé de déduplication.
  • N’exposez jamais le secret : pas dans les logs, pas côté client.
  • Un message.pending peut être suivi d’un message.answered pour le même message une fois la réponse validée — traitez les événements comme des transitions d’état, pas comme des doublons.