Aller au contenu

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.

4.6 Format du payload

{
  "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