Aller au contenu

PD-180 — Expression de besoin : Webhooks pour événements utilisateur

Champ Valeur
Story PD-180
Epic PD-186 (Backend Core)
Projet ProbatioVault-backend
Auteur Claude (étape 0 — gouvernance IA)
Date 2026-03-07

1. Contexte

ProbatioVault s'industrialise vers le marché B2B : cabinets RH, garages automobiles, assurances, SIRH, legaltechs. Ces partenaires intègrent ProbatioVault dans leurs systèmes d'information existants (ERP, SIRH, GED) et ont besoin d'être informés en temps réel lorsqu'un événement métier survient — dépôt de document, scellement probatoire, génération de preuve, partage, révocation.

Aujourd'hui, la seule option pour un système tiers est le polling : interroger périodiquement l'API REST pour détecter les changements. Ce mécanisme est inefficace, coûteux en bande passante, et introduit une latence incompatible avec les workflows automatisés des partenaires (ex. : notification immédiate d'un scellement pour déclencher un workflow d'archivage légal côté client).

Les webhooks sortants sont le standard de l'industrie (Stripe, GitHub, Twilio) pour résoudre ce problème : ProbatioVault notifie proactivement les systèmes tiers en envoyant une requête HTTPS signée vers un endpoint configuré par le partenaire.

2. Problème

Pour les partenaires B2B

  • Le polling continu génère un trafic réseau disproportionné (99% de requêtes sans changement)
  • La latence de détection dépend de la fréquence de polling (compromis coût/réactivité)
  • Chaque partenaire doit implémenter et maintenir sa propre boucle de polling
  • Pas de garantie de détection exhaustive (fenêtre entre deux polls)

Pour ProbatioVault

  • Le polling massif surcharge l'API REST (amplification N partenaires × M fréquence)
  • Aucun mécanisme de notification push n'existe dans l'architecture actuelle
  • L'absence de webhooks est un bloqueur d'adoption pour les intégrateurs B2B qui exigent ce standard

3. Objectif

Implémenter un système de webhooks sortants permettant à ProbatioVault de notifier automatiquement les systèmes tiers lors de la survenue d'événements métier, avec les propriétés suivantes :

  • Sécurisé — Signature HMAC SHA-256, HTTPS obligatoire, anti-rejeu par timestamp
  • Robuste — Livraison asynchrone via BullMQ, retry exponentiel, tolérance aux pannes
  • Auditable — Journal append-only de chaque tentative de livraison, rétention 30 jours
  • Multi-tenant — Isolation stricte par organisation via RLS PostgreSQL
  • Zero-knowledge — Aucune donnée sensible, aucune clé, aucun contenu en clair dans les payloads

4. Périmètre fonctionnel

4.1 Événements couverts (v1 — 7 événements)

É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
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 le compte

4.2 Gestion des abonnements (API REST)

  • Création d'un webhook : URL cible, sélection des événements, génération automatique du secret HMAC
  • Lecture : liste des webhooks de l'organisation, détail d'un webhook
  • Mise à jour : URL, événements souscrits, activation/désactivation
  • Suppression : désactivation puis suppression logique ou physique
  • Rotation du secret : génération d'un nouveau secret HMAC (l'ancien est invalidé immédiatement)
  • Endpoint de test (ping) : envoi d'un événement webhook.ping pour valider la connectivité
  • Replay : ré-émission d'un événement déjà livré (à partir de son event_id)

4.3 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": {}
  }
}

Principe zero-knowledge : le champ data ne contient que des identifiants et métadonnées neutres. Aucun contenu de document, aucune clé cryptographique, aucun blob chiffré.

4.4 Signature et vérification

  • Header : X-ProbatioVault-Signature: t=<timestamp>,v1=<HMAC_SHA256(timestamp.payload, secret)>
  • Le destinataire vérifie : recalcul du HMAC, comparaison timing-safe, rejet si |t - now| > 5 minutes

4.5 Livraison et retry

  • Livraison asynchrone via worker BullMQ dédié
  • Retry exponentiel : 1 min → 5 min → 15 min → 1 h (puis toutes les heures)
  • Maximum 10 tentatives
  • Timeout de livraison : 5 secondes (configurable)
  • Pas de suivi de redirection HTTP (prévention SSRF)
  • Échec final (10 tentatives épuisées) : statut FAILED, notification interne

4.6 Journal des livraisons

  • Enregistrement append-only de chaque tentative : timestamp, event_id, webhook_id, statut HTTP, durée, tentative N/10
  • Rétention : 30 jours
  • Consultable via API REST (historique de livraison par webhook)

5. Contraintes

Contrainte Valeur
Webhooks par organisation Maximum 5
Rate limit événements 100/min par organisation (excédentaires en file)
Rétention logs livraison 30 jours
Protocole HTTPS obligatoire (HTTP refusé)
Timeout livraison 5 secondes (configurable)
Redirections HTTP Refusées (prévention SSRF)
Retry max 10 tentatives, backoff exponentiel
Scope abonnements Organisation (tenant B2B), pas utilisateur B2C
Architecture Stateless NestJS + workers BullMQ
Isolation données RLS PostgreSQL par organisation

6. Invariants de sécurité

ID Invariant
INV-01 Aucune donnée sensible (contenu document, clé crypto, blob chiffré) dans le payload webhook
INV-02 Tout payload est signé HMAC SHA-256 avant envoi — aucune livraison sans signature
INV-03 Le secret HMAC est généré côté serveur, stocké chiffré, jamais exposé après création
INV-04 Toute tentative de livraison est journalisée en append-only AVANT l'envoi
INV-05 Un webhook ne peut accéder qu'aux événements de son organisation (RLS)
INV-06 L'émission d'un webhook ne doit JAMAIS précéder la journalisation probatoire de l'événement
INV-07 Les webhooks ne dépendent pas du HSM et ne modifient aucun état probatoire
INV-08 La comparaison du secret lors de la rotation utilise crypto.timingSafeEqual
INV-09 Le binding organisationnel est extrait du JWT (req.user.orgId), jamais du body
INV-10 HTTPS obligatoire — toute URL en HTTP est rejetée à l'enregistrement et à la livraison

7. Hors périmètre (v1)

Exclusion Justification
UI d'administration des webhooks Story séparée (front-end)
IP allowlist Complexité réseau — sécurité v1 couverte par HMAC + HTTPS
Filtrage avancé par metadata Sélection par event_type suffit pour v1
Abonnements utilisateur B2C Webhooks destinés aux intégrations système B2B
Visualisation DLQ dans UI Consultable via API, UI future
Événements custom (définis par le partenaire) Périmètre fermé aux 7 événements définis

8. Critères de succès

# Critère
1 Les 7 événements déclenchent correctement un webhook vers l'URL configurée
2 La signature HMAC est vérifiable par le destinataire (documentation + exemple de vérification)
3 Le retry exponentiel fonctionne jusqu'à 10 tentatives puis marque FAILED
4 L'isolation RLS empêche tout accès cross-tenant (test d'accès croisé)
5 Le journal des livraisons est consultable et retrace chaque tentative
6 L'endpoint ping valide la connectivité de bout en bout
7 Le replay ré-émet un événement existant avec une nouvelle tentative de livraison
8 La rotation du secret invalide immédiatement l'ancien secret
9 Aucune donnée sensible ne fuit dans les payloads (audit zero-knowledge)
10 Le rate limit à 100 evt/min par org est respecté (excédentaires en file, pas perdus)

9. Risques identifiés

# Risque Impact Mitigation
R1 SSRF via URL webhook malveillante Élevé HTTPS only, pas de redirection, validation URL, timeout strict
R2 Secret HMAC compromis Élevé Rotation de secret, stockage chiffré, timing-safe comparison
R3 Surcharge du worker BullMQ (pic d'événements) Moyen Rate limit 100/min par org, concurrence configurable du worker
R4 Endpoint destinataire lent ou indisponible Moyen Timeout 5s, retry exponentiel, FAILED après 10 tentatives
R5 Fuite de données dans le payload Élevé INV-01 strict, review systématique de chaque event builder
R6 Ordering des événements non garanti Faible Timestamp dans le payload, event_id pour idempotence côté client
R7 Replay abusif (amplification) Moyen Rate limit sur l'API replay, journalisation de chaque replay
R8 Race condition journal/émission (INV-06) Élevé Pattern Outbox : écriture DB transactionnelle puis polling worker

10. Dépendances

Story/Module Nature de la dépendance
PD-21 (BullMQ) Infrastructure de jobs asynchrones — worker webhook dédié
PD-3 (Redis) Backend de file BullMQ + rate limiting
PD-15 (Users/RLS) Schéma organisations + RLS pour isolation multi-tenant
PD-17 (Audit log) Journal append-only — le webhook ne s'émet qu'APRÈS journalisation probatoire
PD-19 (CORS/Headers) Security headers pour l'API REST de gestion des webhooks
PD-172 (Rate limiting) Mécanisme de rate limiting Redis à réutiliser pour le 100 evt/min
PD-28 (Session/JWT) Extraction du binding organisationnel depuis le JWT

Ce document constitue l'entrée pour l'étape 1 (spécification technique).