Documentation du webhook
Dernière mise à jour : 2026-10-14
Avec le forfait Pro ou Entreprise, AgentAO peut envoyer vos alertes d'appels d'offres à votre propre système : chaque résumé (le même que le courriel, à la même fréquence) devient une requête POST en JSON vers l'adresse que vous avez enregistrée dans Paramètres → Notifications.
La requête
- X-AgentAO-Event :
alert.digestpour un résumé,testpour le bouton « Tester ». - X-AgentAO-Delivery : identifiant unique de l'envoi, repris dans
delivery_id. Servez-vous-en pour ignorer un doublon. - X-AgentAO-Signature :
t=<horodatage Unix>,v1=<HMAC-SHA256>, voir plus bas.
POST /votre-adresse HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: AgentAO-Alerts/1.0 (+https://agentao.ca)
X-AgentAO-Event: alert.digest
X-AgentAO-Delivery: 5d0c6c3e-2f7a-4f43-9a51-0f1c1f0a7b21
X-AgentAO-Signature: t=1760479200,v1=8f3b…
{
"event": "alert.digest",
"delivery_id": "5d0c6c3e-2f7a-4f43-9a51-0f1c1f0a7b21",
"sent_at": "2026-10-14T11:00:04.120513+00:00",
"frequency": "daily",
"locale": "fr",
"tenders": [
{
"id": 48213,
"title": "Réfection de pavage — rue Principale",
"organization": "Ville de Laval",
"region": "Laval",
"sector": "Construction",
"closing_date": "2026-11-01T14:00:00",
"closing_date_timezone": "America/Toronto",
"estimated_value": 250000.0,
"url": "https://agentao.ca/dashboard/rfp/48213",
"saved_search": "Pavage Laval"
}
],
"overflow": 0,
"dashboard_url": "https://agentao.ca/dashboard"
}tenders liste au plus 25 appels d'offres ; overflow compte ceux qui correspondent aussi mais ne sont pas listés (ils sont dans le tableau de bord). closing_date est l'heure du Québec, sans fuseau dans la valeur. estimated_value et region peuvent être nuls. saved_search est le nom de la recherche enregistrée qui a trouvé l'appel d'offres, ou nul quand c'est votre profil (secteurs, mots-clés).
Vérifier la signature
La clé de signature (whsec_…) est affichée une seule fois, à l'ajout du webhook ; le bouton « Nouvelle clé » en crée une autre et invalide l'ancienne. La signature est le HMAC-SHA256, en hexadécimal, de <t>.<corps brut>. Calculez-la sur les octets reçus, avant tout décodage, et refusez un horodatage de plus de 5 minutes.
Python
import hashlib, hmac, time
def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t, v1 = parts["t"], parts["v1"]
if abs(time.time() - int(t)) > tolerance:
return False # trop ancien : rejeu possible
expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
# body = les octets bruts reçus, AVANT tout décodage JSONNode.js
import crypto from "node:crypto";
export function verify(secret, header, rawBody, tolerance = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > tolerance) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.`)
.update(rawBody) // Buffer des octets reçus, avant JSON.parse
.digest("hex");
return expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Ce qu'on attend de votre adresse
- Une adresse https publique, sur le port 443. Les redirections ne sont pas suivies.
- Répondre 2xx en moins de 10 secondes ; faites le traitement lourd après avoir répondu.
- Un envoi en échec n'est en général pas répété. Une requête qui vous parvient deux fois (délai dépassé de notre côté après votre réponse) porte le même
delivery_id. Après 5 échecs de suite, ou une réponse 404 ou 410, le canal est désactivé et vous êtes prévenu par courriel ; réactivez-le dans vos paramètres.