Recevoir les webhooks
Déclarer une adresse
Section intitulée « Déclarer une adresse »POST /v1/webhook-endpointsContent-Type: application/json
{"url": "https://votre-serveur.example/tano", "events": ["case.decided", "case.decision_revised"]}La réponse porte le secret de webhook, affiché une seule fois. La liste des événements est dans la référence.
Ce que vous recevez
Section intitulée « Ce que vous recevez »POST /tano HTTP/1.1Content-Type: application/jsonX-Tano-Event: case.decidedX-Tano-Delivery: dlv_…X-Tano-Timestamp: 1790000000X-Tano-Signature: v1=5f2c…
{"data": {"outcome": "approved", "reason_code": "…"}, "object": {"id": "case_…", "type": "case", "external_ref": "client-42", "flow_name": "onboarding_particulier_ci", "environment": "prod"}, "occurred_at": "2026-09-25T10:00:00+00:00", "type": "case.decided"}Pour un dossier, object porte aussi votre référence (external_ref), le parcours et
l’environnement : vous savez de qui il s’agit sans rappeler l’API. Le détail des contrôles se lit
ensuite avec GET /v1/cases/{case_id}/results.
Vérifier la signature
Section intitulée « Vérifier la signature »signature attendue = "v1=" + hex(HMAC-SHA256(secret_webhook, horodatage + "." + corps_brut))import hashlib, hmac, timefrom flask import Flask, abort, request
SECRET = "…" # le secret de webhookapp = Flask(__name__)
@app.post("/tano")def tano(): horodatage = request.headers.get("X-Tano-Timestamp", "") recue = request.headers.get("X-Tano-Signature", "") brut = request.get_data() # le corps brut, avant tout décodage attendue = "v1=" + hmac.new(SECRET.encode(), horodatage.encode() + b"." + brut, hashlib.sha256).hexdigest() if not hmac.compare_digest(attendue, recue) or abs(time.time() - int(horodatage or 0)) > 300: abort(401) livraison = request.headers["X-Tano-Delivery"] # … écarter la livraison si déjà traitée, puis mettre le traitement en file … return "", 204import express from "express";import { createHmac, timingSafeEqual } from "node:crypto";
const SECRET = process.env.TANO_WEBHOOK_SECRET;const app = express();
app.post("/tano", express.raw({ type: "application/json" }), (req, res) => { const horodatage = req.get("X-Tano-Timestamp") ?? ""; const recue = Buffer.from(req.get("X-Tano-Signature") ?? ""); const attendue = Buffer.from( "v1=" + createHmac("sha256", SECRET).update(`${horodatage}.`).update(req.body).digest("hex"), ); const frais = Math.abs(Date.now() / 1000 - Number(horodatage)) <= 300; if (recue.length !== attendue.length || !timingSafeEqual(recue, attendue) || !frais) { return res.sendStatus(401); } const evenement = JSON.parse(req.body.toString("utf8")); // … écarter la livraison si déjà traitée (X-Tano-Delivery), puis mettre en file … res.sendStatus(204);});Répondre vite, traiter ensuite
Section intitulée « Répondre vite, traiter ensuite »Répondez 2xx en moins de cinq secondes : mettez le traitement en file, puis répondez. Seul un
2xx vaut acceptation ; une redirection n’est pas suivie.
Doublons et réessais
Section intitulée « Doublons et réessais »Une livraison peut arriver plus d’une fois. Gardez les X-Tano-Delivery déjà traités et
ignorez les répétitions.
Une livraison en échec est réessayée après 10 secondes, 1 minute, 5 minutes, 30 minutes, 2 heures,
6 heures puis 24 heures. Au huitième échec, l’adresse est suspendue. Depuis la console ou
l’API, vous pouvez consulter les livraisons, en rejouer une
(POST /v1/webhook-endpoints/{id}/replay) et relancer une adresse suspendue
(POST /v1/webhook-endpoints/{id}/resume).
© 2026 Qalebasse · Tano, plateforme de confiance numériquetano.africaDemander un accès[email protected]