PD-180 — Expression de besoin : Webhooks événements utilisateur
| Champ | Valeur |
| Story | PD-180 |
| Epic | PD-186 — Backend Core |
| Projet | ProbatioVault-backend |
| Auteur | Claude (orchestrateur gov) |
| Date | 2026-03-07 |
| Statut | DRAFT |
1. Contexte
ProbatioVault est une plateforme de coffre-fort numérique probatoire qui expose une API REST consommée par des partenaires B2B (cabinets RH, garages automobiles, assureurs, SIRH, legaltechs). Ces partenaires intègrent ProbatioVault dans leurs propres workflows métier pour archiver des documents à valeur probante, générer des preuves composites et gérer des accès partagés.
Aujourd'hui, pour connaître l'état d'un document après dépôt (scellement WORM, ancrage blockchain, génération de preuve), un partenaire doit interroger l'API de manière répétée (polling). Ce modèle ne passe pas à l'échelle : il génère une charge inutile côté serveur, introduit une latence perçue côté client, et complique l'intégration dans des architectures événementielles modernes (EDA, workflow engines, iPaaS).
L'industrialisation B2B exige un mécanisme de notification push — les webhooks sortants — pour informer les systèmes partenaires en temps réel des événements significatifs du cycle de vie documentaire.
2. Problème
Pour les partenaires
- Le polling continu consomme des ressources réseau et compute côté intégrateur.
- La fréquence de polling impose un compromis latence/coût : un polling toutes les 30 secondes sur 10 000 documents génère ~28 800 requêtes/jour par partenaire, dont la quasi-totalité retourne un état inchangé.
- Les architectures événementielles (Kafka, RabbitMQ, n8n, Zapier) ne peuvent pas s'intégrer nativement sans webhooks.
Pour ProbatioVault
- Le polling génère une charge serveur proportionnelle au nombre de partenaires × fréquence, indépendamment du volume réel d'événements.
- L'absence de webhooks est un frein commercial identifié par les prospects B2B lors des phases de due diligence technique.
- Le SLA de notification (temps entre un événement et sa connaissance par le partenaire) n'est pas maîtrisé.
3. Objectif
Implémenter un système de webhooks sortants pour ProbatioVault qui soit :
- Sécurisé — Signature HMAC-SHA256 systématique, anti-rejeu, HTTPS obligatoire, zero-knowledge préservé (aucune donnée sensible, aucune clé dans les payloads).
- Robuste — Livraison asynchrone via BullMQ, retry exponentiel, tolérance aux pannes du récepteur.
- Auditable — Journal append-only de chaque tentative de livraison, rétention 30 jours, traçabilité complète.
- Multi-tenant — Isolation organisationnelle par RLS PostgreSQL, aucune fuite inter-tenant.
- Opérable — API REST de gestion (CRUD, test, replay, rotation secret), sans UI dans cette version.
4. Périmètre fonctionnel
4.1 Événements couverts (v1)
| Événement | Déclencheur |
document.created | Document déposé dans le coffre |
document.sealed | Document scellé WORM + ancrage blockchain initié |
document.anchored | Preuve composite générée (ancrage confirmé) |
proof.generated | Export de la preuve composite disponible |
document.shared | Partage PRE (Proxy Re-Encryption) effectué |
document.revoked | Accès à un document révoqué |
account.device.revoked | Révocation d'un device sur un compte |
4.2 Gestion des abonnements
- Granularité : organisation (tenant B2B). Les utilisateurs individuels B2C sont hors scope v1.
- CRUD complet via API REST :
- Créer un webhook (URL cible, liste d'événements souscrits)
- Lister les webhooks de l'organisation
- Modifier un webhook (URL, événements, état actif/inactif)
- Supprimer un webhook
- Limite : maximum 5 webhooks par organisation.
- Activation/désactivation : un webhook peut être désactivé sans suppression.
- Sélection d'événements : chaque webhook souscrit à un sous-ensemble des 7 événements.
4.3 Endpoint de test (ping)
- L'API expose un endpoint pour déclencher un envoi de test vers le webhook configuré.
- Payload de type
webhook.ping avec structure identique aux événements réels. - Permet au partenaire de valider sa réception avant mise en production.
4.4 Replay
- Un événement déjà livré peut être rejouté manuellement via l'API.
- Le replay génère un nouvel
event_id et un nouveau timestamp, avec référence à l'événement original. - Soumis aux mêmes règles de signature et de retry.
4.5 Rotation du secret
- Le secret HMAC d'un webhook peut être régénéré via l'API.
- L'ancien secret est immédiatement invalidé.
- Les événements en file d'attente au moment de la rotation sont signés avec le nouveau secret.
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "document.sealed",
"timestamp": "2026-03-07T14:30:00.000Z",
"data": {
"doc_id": "...",
"hash": "SHA3-256 du document",
"user_id": "...",
"metadata": {}
}
}
Règle zero-knowledge : le champ data ne contient que des identifiants, des hashes et des métadonnées neutres. Aucun contenu en clair, aucune clé cryptographique, aucun blob chiffré.
4.7 Signature des requêtes
- Header :
X-ProbatioVault-Signature: t=<unix_timestamp>,v1=<hmac_sha256> - Le HMAC est calculé sur
<unix_timestamp>.<body_json> avec le secret du webhook. - Protection anti-rejeu : le récepteur DOIT vérifier que le timestamp est dans une fenêtre de ±5 minutes.
4.8 Livraison et retry
- Livraison asynchrone via un worker BullMQ dédié.
- Politique de retry exponentiel : 1 min → 5 min → 15 min → 1 h, maximum 10 tentatives.
- Les événements excédant le rate limit (100 événements/minute/organisation) restent en file et sont livrés au rythme autorisé.
- Après 10 échecs : l'événement est marqué
FAILED dans le journal.
4.9 Journal des livraisons
- Chaque tentative d'envoi est journalisée : timestamp, webhook_id, event_id, HTTP status, durée, tentative n°X.
- Journal append-only — aucune mise à jour ni suppression.
- Rétention : 30 jours.
- Consultable via l'API REST (liste paginée, filtres par webhook/événement/statut).
5. Contraintes
| Contrainte | Valeur |
| Webhooks par organisation | 5 maximum |
| Rate limit | 100 événements/minute par organisation |
| Rétention journal | 30 jours |
| Signature | HMAC-SHA256 systématique |
| Transport | HTTPS obligatoire, HTTP refusé |
| Timeout | 5 secondes (configurable) |
| Redirections | Refusées (pas de suivi automatique) |
| Retry | Exponentiel, max 10 tentatives |
| Isolation | RLS PostgreSQL par organisation |
| Architecture | Stateless NestJS + workers BullMQ asynchrones |
| Zero-knowledge | Garanti — aucune donnée sensible dans les payloads |
| Indépendance HSM | Les webhooks ne dépendent PAS du HSM |
| Non-SPOF | Le système de webhooks ne doit pas introduire de SPOF |
| Immutabilité probatoire | Les webhooks ne modifient JAMAIS un état probatoire |
6. Invariants de sécurité
| ID | Invariant |
| INV-01 | Zero-knowledge payload — Aucune donnée sensible (contenu, clé, blob chiffré) dans le payload webhook. Seuls identifiants, hashes et métadonnées neutres sont transmis. |
| INV-02 | Signature systématique — Tout envoi webhook DOIT porter le header X-ProbatioVault-Signature avec HMAC-SHA256 valide. Aucun envoi non signé. |
| INV-03 | Anti-rejeu — Le timestamp de signature est inclus dans le calcul HMAC. Le récepteur peut rejeter les requêtes hors fenêtre ±5 min. |
| INV-04 | HTTPS obligatoire — L'URL cible DOIT être HTTPS. Toute URL HTTP est rejetée à l'enregistrement ET à l'envoi. Pas de fallback. |
| INV-05 | Pas de redirection — Le worker ne suit pas les redirections HTTP (301, 302, 307, 308). Un redirect est traité comme un échec. |
| INV-06 | Journalisation avant envoi — L'intention d'envoi est journalisée dans le log append-only AVANT la tentative HTTP. Pattern outbox. |
| INV-07 | Journal append-only — Le journal des livraisons est en insertion seule. Aucun UPDATE, aucun DELETE sur les entrées de livraison. |
| INV-08 | Isolation multi-tenant — RLS PostgreSQL sur toutes les tables webhook. Une organisation ne peut ni lire, ni modifier, ni déclencher les webhooks d'une autre organisation. |
| INV-09 | Secret non exposé — Le secret HMAC n'est JAMAIS retourné en clair après création. L'API retourne uniquement les 4 derniers caractères pour identification. |
| INV-10 | Binding organisationnel — L'organisation est déterminée par le JWT (req.user.orgId), JAMAIS par un paramètre du body ou de l'URL. |
| INV-11 | Timing-safe comparison — La comparaison du secret HMAC utilise crypto.timingSafeEqual, jamais ===. |
| INV-12 | Non-mutation probatoire — Le système de webhooks ne modifie aucun état probatoire (document, preuve, ancrage, scellement). Il est strictement en lecture + notification. |
| INV-13 | Indépendance HSM — Le système de webhooks ne dépend d'aucune opération HSM. Le secret HMAC est géré indépendamment de la PKI probatoire. |
| INV-14 | Événement post-journalisation uniquement — Un webhook ne peut être déclenché que pour un événement déjà journalisé dans le système probatoire. Aucune notification pour un événement non encore persisté. |
7. Hors périmètre
| Élément | Raison |
| Filtrage avancé par metadata | Complexité v2 — nécessite un DSL de filtrage |
| UI d'administration webhooks | Story séparée (frontend) |
| IP allowlist | Complexité opérationnelle, v2 |
| Visualisation DLQ dans UI | Story séparée (frontend) |
| Webhooks utilisateur B2C | Pas de cas d'usage identifié en v1 |
| Transformation de payload | Les partenaires consomment le format standard |
| Webhooks entrants (inbound) | Hors périmètre — ProbatioVault est la source, pas le sink |
| Événements batch (bulk) | Chaque événement est livré individuellement |
8. Critères de succès
| ID | Critère |
| CS-1 | Un partenaire B2B peut enregistrer un webhook via l'API et recevoir les 7 types d'événements. |
| CS-2 | Chaque requête webhook porte une signature HMAC-SHA256 vérifiable par le partenaire. |
| CS-3 | Un événement échoué est retryé selon la politique exponentielle jusqu'à 10 tentatives. |
| CS-4 | Le journal des livraisons est consultable via l'API avec pagination et filtres. |
| CS-5 | L'endpoint ping permet de tester la réception sans événement réel. |
| CS-6 | Le replay d'un événement passé fonctionne avec un nouvel event_id. |
| CS-7 | La rotation du secret invalide immédiatement l'ancien secret. |
| CS-8 | Aucune fuite inter-tenant : un test d'accès croisé RLS échoue systématiquement. |
| CS-9 | Le rate limit (100 evt/min/org) est appliqué — les événements excédentaires sont différés, pas perdus. |
| CS-10 | Le pipeline CI/CD (lint, tests, Sonar) passe au vert. |
9. Risques identifiés
| ID | Risque | Impact | Probabilité | Mitigation |
| R1 | Surcharge worker — Pic d'événements (dépôt batch) saturant le worker BullMQ | Moyen | Moyenne | Rate limit par organisation + concurrence configurable du worker + monitoring queue depth |
| R2 | Endpoint partenaire instable — Retries massifs sur endpoints lents/down | Moyen | Haute | Timeout strict 5s + backoff exponentiel + désactivation automatique après N échecs consécutifs (v2) |
| R3 | Fuite de données dans le payload — Un événement futur expose accidentellement des données sensibles | Critique | Faible | INV-01 contractualisé + review systématique de chaque nouvel event_type + tests d'assertion sur les champs payload |
| R4 | Rotation secret pendant livraison — Événements en file signés avec l'ancien secret | Faible | Faible | Les événements en file sont signés au moment de l'envoi (pas de la mise en file), donc avec le secret courant |
| R5 | Replay abusif — Un attaquant ayant accès à l'API replay des événements en boucle | Faible | Faible | Rate limit sur l'endpoint replay + audit log des replays + authentification JWT standard |
| R6 | Latence de livraison — Délai entre l'événement métier et la notification webhook | Moyen | Moyenne | Monitoring du lag moyen de la queue + alerte si p99 > seuil configurable |
| R7 | SSRF via URL webhook — Un attaquant enregistre une URL pointant vers un service interne | Critique | Faible | Validation URL à l'enregistrement : blocage IP privées (RFC 1918), localhost, metadata cloud (169.254.x.x) |
10. Dépendances
| Story/Module | Nature de la dépendance |
| PD-13 (NestJS init) | Framework applicatif — modules, guards, interceptors |
| PD-14 (TypeORM) | ORM et migrations — schéma des tables webhook |
| PD-15 (Schema users) | Table organisations — FK pour l'isolation multi-tenant |
| PD-3 (Redis/BullMQ) | Infrastructure queue — worker dédié webhooks |
| PD-28 (Session) | Authentification JWT — binding organisationnel |
| PD-19 (CORS/Security) | Headers de sécurité — configuration des requêtes sortantes |
| PD-31 (Audit log) | Journal d'audit — les événements webhook s'appuient sur les événements déjà journalisés |
| PD-60 (Document upload) | Événement document.created — source du premier event type |
| PD-55 (Worker ancrage) | Événements document.sealed, document.anchored — source des events blockchain |
| PD-41 (PRE) | Événement document.shared — source de l'event partage |