Diese Seite ist noch nicht in Ihre Sprache übersetzt: sie wird auf Französisch angezeigt.
Webhooks
Un webhook prévient un autre outil quand quelque chose arrive, sans qu'il ait à demander. Toutadmin en émet 8, signés, réessayés, et jamais vers votre réseau interne par inadvertance. Cette page couvre l'enregistrement, la signature, la file, les réessais, la désactivation automatique, et la charge utile exacte de chaque événement.
Enregistrer une destination#
Intégrations → Webhooks, réservé à l'administration : ouvrir une porte de sortie sur les données de l'entreprise ne se délègue pas. Quatre champs :
| Champ | Ce qu'il fait |
|---|---|
| Libellé | Pour vous, dans la liste. |
| Adresse | Où poster. https exigé — voir ci-dessous. |
| Secret | Sert à signer. Chiffré en base, il ne ressort jamais : l'écran n'en montre que l'existence. |
| Événements | Ceux que cette destination écoute. Une destination qui écoute tout reçoit tout. |
Ce que l'adresse doit être#
L'adresse est validée avant d'être acceptée, et deux refus sont volontaires :
-
httpen clair est refusé. La charge utile et sa signature circuleraient lisibles : quiconque écoute le réseau lit les données et apprend à forger une requête signée. -
Une adresse du réseau interne est refusée.
localhost,127.0.0.0/8,10.0.0.0/8,192.168.0.0/16,172.16.0.0/12,169.254.0.0/16,0.0.0.0,::1, et les suffixes.localhost,.internal,.local.
Sans ce contrôle, un webhook devient un moyen de faire émettre au serveur des requêtes vers des machines qu'il est seul à joindre — service de métadonnées d'un hébergeur, console d'administration, base interne. C'est la falsification de requête côté serveur. Une case à cocher lève le refus quand votre destination est vraiment sur le réseau interne : le choix est alors explicite et consigné.
Les 8 événements#
| Clé | Quand | Charge utile |
|---|---|---|
facture.creee |
À la création d'une facture, client ou fournisseur. | id, reference, libelle, sens, montant_ht, devise, echeance |
facture.payee |
Au passage au statut « Payée », et à ce passage seulement. | id, reference, libelle, sens, montant_ttc, devise |
absence.approuvee |
À l'approbation d'une absence. | id, type, du, au, jours |
membre.arrive |
Quand l'administration ajoute un membre. | id, prenom, nom, email, poste, contrat |
membre.parti |
À la désactivation d'un compte, à la main ou par fin de contrat. | id, prenom, nom, email, motif |
document.signe |
Quand le dernier signataire a signé, pas au premier. | id, titre, nature, empreinte, signataires |
ticket.ouvert |
À l'ouverture d'un ticket de support. | id, reference, sujet, priorite, origine |
sauvegarde.echec |
Quand l'externalisation d'une sauvegarde échoue. | destination, fichier, motif |
Ce que la charge utile ne contient jamais#
- Le motif d'une absence : il reste dans l'entreprise.
- Le mot de passe temporaire d'un membre qui arrive.
- Le contenu d'un document signé — seulement son empreinte.
- Tout secret et toute empreinte de mot de passe.
membre.parti porte un motif qui vaut desactivation
(geste d'un administrateur) ou fin_de_contrat (balayage automatique).
Réactiver un compte n'émet rien : une réactivation n'est pas une arrivée.
La forme de la requête#
POST /votre-adresse HTTP/1.1
content-type: application/json
x-toutadmin-event: facture.payee
x-toutadmin-signature: sha256=4f1c…
user-agent: Salarie-Member-Webhook/1
{
"event": "facture.payee",
"at": "2026-03-14T09:21:06+00:00",
"data": {
"id": 418,
"reference": "FA-2026-0117",
"libelle": "Prestation mars",
"sens": "client",
"montant_ttc": 4200,
"devise": "EUR"
}
}
L'enveloppe est toujours la même : event, at (UTC), et
data propre à l'événement. Écrivez votre lecteur sur l'enveloppe, pas sur
la position des champs.
Vérifier la signature#
x-toutadmin-signature vaut sha256= suivi du HMAC-SHA256 du
corps brut, clé = votre secret. Signez les octets reçus, pas le résultat
d'une relecture JSON : un espace de différence change la signature.
// Node
const crypto = require('crypto');
function verifie(corpsBrut, entete, secret) {
const attendu = 'sha256=' + crypto.createHmac('sha256', secret).update(corpsBrut).digest('hex');
const a = Buffer.from(attendu);
const b = Buffer.from(String(entete || ''));
// Longueurs différentes : timingSafeEqual lèverait. On refuse d'abord.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// PHP
function verifie(string $corpsBrut, string $entete, string $secret): bool
{
$attendu = 'sha256=' . hash_hmac('sha256', $corpsBrut, $secret);
return hash_equals($attendu, $entete); // jamais ===
}
# Python
import hmac, hashlib
def verifie(corps: bytes, entete: str, secret: str) -> bool:
attendu = 'sha256=' + hmac.new(secret.encode(), corps, hashlib.sha256).hexdigest()
return hmac.compare_digest(attendu, entete)
Une comparaison ordinaire s'arrête au premier caractère qui diffère. Le temps de réponse
dit alors combien de caractères étaient justes, et la signature se devine octet par
octet. timingSafeEqual, hash_equals,
compare_digest : selon le langage, mais jamais ==.
La file et les réessais#
Un événement n'est pas envoyé pendant la requête qui l'a produit : il est mis en file. Payer une facture ne doit pas attendre que votre serveur réponde — ni échouer parce qu'il est en panne.
| Règle | Valeur |
|---|---|
| Délai d'attente d'une livraison | 8 secondes |
| Tentatives avant abandon | 5 |
| Attente entre deux tentatives | 2, 4, 8, 16 minutes, plafonnée à 60 |
| Échecs consécutifs avant désactivation de la destination | 20 |
Une réponse 2xx vaut réussite. Tout le reste — 4xx, 5xx, expiration, connexion refusée, certificat invalide — est un échec qui sera réessayé, puis abandonné après cinq tentatives. La livraison garde alors le statut « Abandonné » et la dernière erreur, lisible dans l'écran.
Après vingt échecs consécutifs, la destination est désactivée. Sans cela, une adresse morte ferait vivre indéfiniment une file qui grossit. La réactiver se fait d'un clic, une fois votre service réparé.
Quand la file est vidée#
| Édition | Déclencheur |
|---|---|
| Node | Le balayage interne du serveur, en permanence. |
| PHP | tools/cron.php, au rythme de votre cron. |
Sur un hébergement limité à un passage tous les quarts d'heure, les webhooks partent avec ce délai. C'est la seule différence de comportement observable entre les deux éditions.
Essayer sans attendre un vrai événement#
Chaque destination a un bouton d'essai : il envoie une charge utile de test sur le premier
événement écouté, avec {"essai": true} dans les données. La livraison passe
par la file normale — vous vérifiez donc en une fois l'adresse, le certificat, la
signature et votre lecteur.
Écrire un lecteur qui tient#
- Répondez 2xx tout de suite, puis travaillez. Un traitement long fait expirer la livraison, qui sera réessayée — et vous traiterez deux fois.
- Attendez-vous aux doublons. Un réessai après une réponse perdue renvoie la même charge utile. Déduisez une clé de
event+data.idet ignorez ce que vous avez déjà vu. - Vérifiez la signature avant de lire le corps, pas après.
- Ignorez les champs inconnus. Une charge utile peut gagner des champs ; elle n'en perd pas.
- N'ordonnez rien sur l'arrivée. Deux événements réessayés différemment n'arrivent pas dans l'ordre où ils se sont produits.
atdit l'ordre réel.
Quand rien n'arrive#
| Symptôme | Cause la plus fréquente |
|---|---|
| Aucune livraison, pas même en attente | La destination n'écoute pas cet événement, ou elle est désactivée. |
| Tout reste « En attente » | Édition PHP : le cron n'est pas installé ou n'est pas exécuté. |
| « secret illisible » | Le secret d'instance a changé : le secret chiffré n'est plus déchiffrable. Ressaisissez-le. |
| Signature qui ne correspond jamais | Vous signez le JSON relu, pas le corps brut reçu. |
| Abandons en série sur une adresse qui marche | Certificat TLS invalide ou chaîne incomplète côté destination. |
L'écran Intégrations montre, pour chaque destination, le dernier statut, la dernière tentative et le nombre d'échecs consécutifs. C'est là qu'il faut regarder d'abord — avant les journaux du serveur.
L'API de lecture
Les neuf ressources, les jetons et leurs portées.
Sécurité
Ce qui protège l'instance, et pourquoi.
Toutadmin-Dokumentation — erstellt am 2026-09-13. Eigenständige Website, unabhängig von der Software.