12 · webhooks

Webhooks : recevoir et émettre des événements

Deux sens : vos systèmes déclenchent l'agent (entrant), et la plateforme vous prévient des transferts et des fins de conversation (sortant). Dans les deux cas, chaque message est signé en HMAC-SHA256 sur le corps brut.

  • Entrant : POST /api/v1/webhooks/{tenantId}/{sourceId}. Créez une source (un secret est généré et affiché une seule fois), puis un déclencheur : type d'événement → bot → canal (WhatsApp ou SMS) → modèle de message.
  • En-têtes : X-Webhook-Signature: sha256=<hex> (ou X-Hub-Signature-256), X-Webhook-Timestamp (anti-rejeu, tolérance de 300 s par défaut), X-Webhook-Id (idempotence).
  • Le type d'événement est lu dans le champ type, event ou event_type du JSON. Réponse 202 { ok, matched } ; toute signature invalide renvoie 401 sans détail.
  • Sortant : Paramètres → Webhooks. Événements : handoff.opened, handoff.taken, handoff.closed, conversation.closed.
  • Chaque appel sortant est un POST JSON { event, at, … } avec les en-têtes X-Recla-Event et, si un secret est défini, X-Recla-Signature: sha256=<hex>. Délai de 8 s, réponse 2xx attendue ; le dernier statut est visible dans l'interface.
  • Le bouton « Tester » envoie un événement webhook.test. URL en http ou https, sans identifiants intégrés.
Webhook entrant — signer et envoyer (cURL)
BODY='{"type":"order.shipped","id":"evt_42","msisdn":"+2250700000000","order":"CMD-2291"}'
SECRET="secret-de-la-source"
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')

curl -X POST https://api.votredomaine.com/api/v1/webhooks/TENANT_ID/SOURCE_ID \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: sha256=$SIG" \
  -H "X-Webhook-Timestamp: $(date +%s)" \
  -H "X-Webhook-Id: evt_42" \
  -d "$BODY"
Webhook sortant — vérifier la signature (Node.js)
import crypto from "node:crypto";
import express from "express";

const app = express();
// Il faut le corps BRUT : la signature porte sur les octets reçus.
app.post("/recla-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto.createHmac("sha256", process.env.WEBHOOK_SECRET).update(req.body).digest("hex");
  const received = req.get("X-Recla-Signature") ?? "";
  const ok = received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);

  const { event, at, ...data } = JSON.parse(req.body.toString());
  console.log(req.get("X-Recla-Event"), event, at, data);
  res.sendStatus(200); // 2xx attendu sous 8 secondes
});
app.listen(3000);