PD-180 — Spécification canonique des webhooks sortants d’événements utilisateur (corrigée v3)¶
1. Objectif¶
La User Story PD-180 définit un mécanisme contractuel de notification push B2B par webhooks sortants pour les événements du cycle de vie documentaire ProbatioVault, afin de :
- supprimer la dépendance au polling côté partenaires ;
- garantir des notifications sécurisées, traçables, multi-tenant et robustes ;
- fournir des capacités d’exploitation API (CRUD, ping, replay, rotation secret) sans interface UI en v1.
Cette spécification est normative et exhaustive. Toute règle ci-dessous est testable.
2. Périmètre / Hors périmètre¶
Inclus¶
- Webhooks sortants au niveau organisation (tenant B2B).
- Gestion API des abonnements webhook : création, lecture, mise à jour, suppression, activation/désactivation.
- Couverture des événements v1 :
document.createddocument.sealeddocument.anchoredproof.generateddocument.shareddocument.revokedaccount.device.revoked- Endpoint de test
webhook.ping. - Replay manuel d’un événement livré avec nouvel
event_id. - Rotation du secret HMAC avec invalidation immédiate de l’ancien.
- Signature systématique HMAC-SHA256.
- Journal append-only des tentatives de livraison, consultable via API.
- Isolation multi-tenant par organisation.
Exclu¶
- Webhooks entrants (inbound).
- UI d’administration.
- Filtrage avancé par metadata.
- IP allowlist administrable côté UI.
- Visualisation DLQ en UI.
- Webhooks B2C individuels.
- Transformation de payload.
- Événements batch/bulk.
3. Définitions¶
- Webhook sortant : requête HTTP émise par ProbatioVault vers une URL partenaire.
- Tenant / organisation : entité B2B isolée logiquement.
- Event type : type d’événement métier notifié.
- Replay : réémission manuelle d’un événement historique.
- Ping : événement de test non métier (
webhook.ping). - Tentative de livraison : essai unique d’envoi HTTP.
- Journal append-only métier : registre métier en insertion seule (aucun UPDATE/DELETE applicatif).
- Purge technique : suppression d’entrées >30 jours via mécanisme d’infrastructure (cron/TTL), distincte du métier.
- RLS : Row-Level Security PostgreSQL, isolation par organisation.
- Anti-rejeu : validation d’une fenêtre temporelle de signature.
- État terminal : état sans transition sortante autorisée.
4. Invariants (non négociables)¶
| ID | Règle | Justification |
|---|---|---|
| INV-01 | Le payload webhook ne contient aucune donnée sensible (contenu en clair, clé, blob chiffré, secret). | Zero-knowledge contractuel. |
| INV-02 | Toute requête webhook sortante est signée via X-ProbatioVault-Signature en HMAC-SHA256. | Authenticité et intégrité. |
| INV-03 | La signature inclut un timestamp Unix (t) et permet le rejet hors fenêtre anti-rejeu ±5 minutes. | Protection contre replay réseau. |
| INV-04 | L’URL cible DOIT utiliser le schéma https. Tout autre schéma (http, ftp, file, data, javascript, etc.) est rejeté à l’enregistrement et à l’envoi. | Confidentialité transport et réduction de surface d’attaque. |
| INV-05 | Les réponses HTTP 301/302/307/308 sont traitées en échec ; aucune redirection suivie. | Évite redirection abusive/SSRF indirect. |
| INV-06 | L’intention d’envoi est journalisée avant tentative de livraison. | Traçabilité complète et auditabilité. |
| INV-07 | Journal append-only métier : aucun UPDATE/DELETE applicatif autorisé. La purge technique par rétention (>30 jours) via cron/TTL infrastructure est autorisée et n’est pas une mutation métier. Périmètre de purge : la purge >30j s’applique aux entrées du journal de tentatives de livraison. Les intentions de livraison (registre d’événements webhook) sont conservées sans limite de durée. | Immutabilité métier + conformité rétention. |
| INV-08 | L’isolation tenant est strictement organisationnelle : aucune lecture/modification cross-tenant. | Conformité multi-tenant. |
| INV-09 | Le secret HMAC n’est jamais retourné en clair après création/rotation ; seuls les 4 derniers caractères sont affichables. | Réduction d’exposition secret. |
| INV-10 | L’organisation est dérivée du JWT (orgId) et jamais d’un paramètre client. | Anti-usurpation inter-tenant. |
| INV-11 | Le système webhooks n’altère aucun état probatoire (document/preuve/ancrage/scellement). | Séparation stricte notification vs preuve. |
| INV-12 | Un webhook est émis uniquement pour un événement déjà journalisé côté système source. | Cohérence causale des notifications. |
| INV-13 | Modèle d’états contractuel : toute transition non listée est interdite explicitement. | Évite ambiguïtés de machine à états. |
| INV-14 | Crash pré-commit : rollback complet, aucun artefact persistant ; crash post-commit : rattrapage asynchrone obligatoire. | Atomicité DB + asynchrone. |
| INV-15 | Protection SSRF obligatoire à l’enregistrement ET à l’envoi : validation schéma/host, résolution DNS A/AAAA avant envoi, blocage des IP privées (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback (127.0.0.0/8, ::1), link-local (169.254.0.0/16, fe80::/10), metadata cloud (169.254.169.254). Mitigation DNS rebinding : le worker résout le DNS, valide l’IP, puis établit la connexion TCP vers l’IP résolue (IP pinning — pas de seconde résolution DNS par le client HTTP). Si le client HTTP ne supporte pas le pinning IP, effectuer une double résolution avec comparaison. | Prévention d’accès réseau interne et rebinding. |
5. Contrat fonctionnel et technique¶
5.1 Modèle de données contractuel (formats et contraintes)¶
| Donnée | Format / encodage | Taille / longueur | Validation | Comportement si invalide |
|---|---|---|---|---|
webhook_id | UUID v4 texte | 36 | regex UUIDv4 | Rejet 400 |
org_id | UUID v4 texte | 36 | dérivé JWT uniquement | Rejet 401/403 |
event_id | UUID v4 texte | 36 | regex UUIDv4 | Rejet 400 |
event_type | Enum string | 1..64 | [a-z0-9._]+ + enum v1 + webhook.ping | Rejet 400 |
timestamp (payload) | RFC3339 UTC ISO-8601 avec ms, suffixe Z | 24 | date UTC parseable | Rejet 400 |
t (signature) | Unix timestamp secondes (décimal) | 1..13 chiffres | entier valide | Rejet partenaire attendu 401 |
v1 (signature) | HMAC-SHA256 hex lowercase | 64 | ^[a-f0-9]{64}$ | Rejet partenaire attendu 401 |
X-ProbatioVault-Signature | t=<unix>,v1=<hex64> | <=128 | ^t=\d{1,13},v1=[a-f0-9]{64}$ | Tentative en échec |
target_url | URL absolue HTTPS RFC3986 | <= 2048 chars | schéma https + SSRF checks | Rejet 400 |
data.doc_id | UUID v4 texte | 36 | regex UUIDv4 | Rejet 400 |
data.hash | SHA3-256 encodé hex lowercase | 64 chars exacts | ^[a-f0-9]{64}$ | Rejet 400 |
data.user_id | UUID v4 texte | 36 | regex UUIDv4 | Rejet 400 |
data.device_id | UUID v4 texte | 36 | regex UUIDv4 (variante B uniquement) | Rejet 400 |
data.metadata | Objet JSON | <= 4096 bytes UTF-8 | JSON valide | Rejet 400 |
Schéma JSON whitelist (strict)¶
Le schéma payload dépend de la catégorie de l'événement. Deux variantes sont définies :
Variante A — Événements documentaires (document.created, document.sealed, document.anchored, proof.generated, document.shared, document.revoked) :
{
"event_id": "uuid-v4",
"event_type": "string",
"timestamp": "rfc3339-utc",
"data": {
"doc_id": "uuid-v4",
"hash": "sha3-256-hex-lowercase-64",
"user_id": "uuid-v4",
"metadata": {}
}
}
Variante B — Événements non-documentaires (account.device.revoked) :
{
"event_id": "uuid-v4",
"event_type": "account.device.revoked",
"timestamp": "rfc3339-utc",
"data": {
"device_id": "uuid-v4",
"user_id": "uuid-v4",
"metadata": {}
}
}
Variante C — Ping (webhook.ping) :
{
"event_id": "uuid-v4",
"event_type": "webhook.ping",
"timestamp": "rfc3339-utc",
"data": {
"user_id": "uuid-v4",
"metadata": {}
}
}
Règle : additionalProperties: false au niveau racine et au niveau data pour chaque variante. Le champ event_type détermine la variante applicable.
5.2 Canonicalisation JSON et signature¶
- Canonicalisation obligatoire : sérialisation déterministe via
JSON.stringify()ECMAScript standard (ordre des clés d’insertion, sans espaces, sans indentation). - Ordre de construction de l’objet à signer :
event_idevent_typetimestampdataavec ordre interne selon variante :- Variante A (documentaire) :
doc_id,hash,user_id,metadata - Variante B (account.device.revoked) :
device_id,user_id,metadata - Variante C (webhook.ping) :
user_id,metadata
- Variante A (documentaire) :
- Message signé :
<unix_timestamp>.<json_stringify_output> - Calcul :
v1 = HMAC_SHA256(signing_key, message)avec sortie hex lowercase. - Clé de signature :
signing_key = SHA-256(secret_brut), soit exactement la valeursecret_sha256stockée en DB (cf. §5.3). - Obligation partenaire : le partenaire reçoit le secret brut
Sà la création/rotation. Pour vérifier les signatures, il DOIT calculerK = SHA-256(S)une seule fois, puis utiliserKcomme clé HMAC :expected = HMAC_SHA256(K, t + "." + body). Cette obligation est documentée dans la réponse API de création/rotation. - Construction payload pour signature : l'objet payload DOIT être construit programmatiquement dans l'ordre spécifié ci-dessus immédiatement avant sérialisation. Aucun middleware, ORM ou désérialisation intermédiaire ne doit reconstruire l'objet entre la construction et la sérialisation.
5.3 Secret HMAC (génération, stockage, usage)¶
- Secret brut généré par CSPRNG :
crypto.randomBytes(32)minimum (32 bytes, peut être supérieur). - Secret brut affiché une seule fois à la création/rotation (jamais relu ensuite). Si le partenaire ne capture pas le secret brut, le seul recours est de déclencher une nouvelle rotation. Aucun mécanisme de récupération n’est prévu (by design — réduction de surface d’exposition).
- En DB : stockage du hash SHA-256 (
secret_sha256, hex lowercase 64). - Comparaison de secret : uniquement via hash.
- Clé de signature opérationnelle :
signing_key = secret_sha256(le hash SHA-256 stocké en DB est utilisé directement comme clé HMAC, sans dérivation supplémentaire). Le partenaire calculeK = SHA-256(secret_brut_reçu)et utiliseKpour vérifierHMAC-SHA256(K, message). - Obligation d’exécution : le worker relit
secret_sha256en DB à chaque tentative d’envoi ; aucun cache longue durée du secret/hash en mémoire worker n’est autorisé.
5.4 Politique de succès/échec HTTP et retry¶
- Succès livraison : tout code HTTP
2xx=> étatDELIVERED. - Timeout HTTP de livraison : 5 secondes. Au-delà, la tentative est classée en échec timeout.
- Échec livraison : tout code non
2xx(incluant1xx,3xx,4xx,5xx), timeout (>5s), erreur réseau, erreur TLS, erreur DNS, blocage SSRF => tentative échouée. - Les réponses partenaire
401(ex. validation signature/timestamp côté partenaire) sont traitées comme échec retriable. - Politique de retry (10 tentatives max, séquence contractuelle) :
1min, 5min, 15min, 1h, 1h, 1h, 1h, 1h, 1h, 1h - Au-delà de la 10e tentative échouée : état terminal
FAILED.
5.5 Flux nominaux¶
Flux A — Création d’un webhook¶
- Client authentifié organisation envoie
target_url+event_type[]. - Système vérifie quota organisationnel (max 5 webhooks).
- Système valide URL (
httpsobligatoire + SSRF checks), liste d’événements. Unicité non imposée : deux webhooks avec la mêmetarget_urlmais desevent_typesdifférents sont autorisés. - Système génère secret CSPRNG (>=32 bytes), stocke
secret_sha256. La réponse de création retourne le secret brut en clair une seule et unique fois (avec instruction partenaire : « calculerK = SHA-256(S)pour vérification HMAC »). Les réponses API ultérieures (GET, LIST) n’exposent que le masque (4 derniers caractères). - Webhook devient éligible à livraison selon état (
ACTIVE/INACTIVE).
Flux B — Mise à jour webhook (URL, événements, état)¶
Attributs modifiables : target_url, event_types[], état (transitions autorisées selon §5.7). Attributs immuables : webhook_id, org_id, secret_sha256, created_at.
- Client authentifié met à jour un ou plusieurs attributs modifiables.
- Revalidation complète (
https, SSRF, enum événements, contraintes format). - Changement d’état appliqué immédiatement.
- Les livraisons futures respectent l’état courant.
Flux C — Suppression webhook¶
- Client authentifié supprime un webhook de son organisation.
- État devient
DELETED. DELETEDest terminal : aucune transition sortante autorisée.
Flux D — Émission événement métier¶
- Événement source est déjà journalisé.
- Système crée l’intention de livraison (append-only métier).
- Livraison asynchrone vers chaque webhook
ACTIVEabonné. - Avant chaque envoi : relecture
secret_sha256en DB + résolution DNS A/AAAA + contrôles SSRF. - Tentative journalisée avec statut, durée, code HTTP/résultat, numéro de tentative.
- Code
2xx=>DELIVERED. - Sinon => retry selon séquence ; au max =>
FAILED.
Flux E — Ping¶
- Client déclenche test sur webhook.
- Système émet
webhook.pingavec même structure contractuelle. - Signature, timeout (5s), retry, SSRF checks et journalisation identiques au flux métier.
- Le ping est émis indépendamment des
event_typessouscrits par le webhook — tout webhook peut être pingué. - Le ping génère une intention de livraison soumise au rate limit CA-13 (comptabilisée comme 1 intention).
Flux F — Replay¶
Critères d’éligibilité au replay : un événement est éligible au replay si et seulement si : - (1) il appartient à l’organisation du demandeur (dérivée du JWT) ; - (2) son état de livraison est DELIVERED ou FAILED ; - (3) il date de moins de 30 jours (les événements purgés retournent 404). Les événements en état PENDING, IN_PROGRESS ou RETRY_SCHEDULED ne sont pas replayables (retour 409 Conflict).
- Client demande replay d’un événement historique éligible.
- Système vérifie les critères d’éligibilité ci-dessus.
- Système crée une nouvelle notification avec :
event_idnouveau,timestampnouveau,- lien
original_event_idconservé côté journal interne/API de traçabilité. - L'événement replayed est émis vers tous les webhooks
ACTIVEabonnés à l'event_typeau moment du replay (pas au moment de l'événement original). - Traitement identique (signature, livraison, retry, journalisation).
- Le replay génère des intentions de livraison soumises au rate limit CA-13.
Flux G — Rotation du secret¶
- Client déclenche rotation.
- Nouveau secret actif immédiatement.
- Ancien secret invalide immédiatement.
- Les événements déjà en file non envoyés sont signés avec le nouveau secret.
- Note explicite : chaque tentative relit
secret_sha256en DB ; aucune tentative ne doit utiliser un secret mis en cache avant rotation.
5.6 Atomicité multi-composant (DB + asynchrone)¶
| Scope | Synchrone/Async | Garantie contractuelle |
|---|---|---|
| Écriture événement/intention de livraison | Synchrone transactionnelle | ACID |
| Tentatives HTTP webhook | Async post-commit | Idempotent, retry-safe |
| Journal de tentatives | Async/séquentiel par tentative | Append-only métier |
| Crash pré-commit | N/A | Rollback complet |
| Crash post-commit | N/A | Rattrapage asynchrone obligatoire |
5.7 Modèle d’états et transitions¶
A. État webhook - ACTIVE → INACTIVE : AUTORISÉE - ACTIVE → DELETED : AUTORISÉE - INACTIVE → ACTIVE : AUTORISÉE - INACTIVE → DELETED : AUTORISÉE - DELETED → * : INTERDITE (terminal)
B. État livraison - PENDING → IN_PROGRESS : AUTORISÉE - IN_PROGRESS → DELIVERED : AUTORISÉE (si HTTP 2xx) - IN_PROGRESS → RETRY_SCHEDULED : AUTORISÉE (si non 2xx/timeout/erreur) - RETRY_SCHEDULED → IN_PROGRESS : AUTORISÉE - RETRY_SCHEDULED → FAILED : AUTORISÉE (tentative 10 échouée) - DELIVERED → * : INTERDITE - FAILED → * : INTERDITE (replay crée un nouvel événement)
5.8 Diagrammes Mermaid¶
Diagramme d’états — Webhook (INV-13)¶
stateDiagram-v2
[*] --> ACTIVE : Création (Flux A)
ACTIVE --> INACTIVE : Désactivation (Flux B)
INACTIVE --> ACTIVE : Réactivation (Flux B)
ACTIVE --> DELETED : Suppression (Flux C)
INACTIVE --> DELETED : Suppression (Flux C)
DELETED --> [*]
note right of DELETED
État terminal — aucune transition
sortante autorisée (INV-13)
end note Diagramme d’états — Livraison (INV-06, INV-13, INV-14)¶
stateDiagram-v2
[*] --> PENDING : Intention journalisée (INV-06)
PENDING --> IN_PROGRESS : Worker prend en charge
IN_PROGRESS --> DELIVERED : HTTP 2xx
IN_PROGRESS --> RETRY_SCHEDULED : Non-2xx / timeout / erreur
RETRY_SCHEDULED --> IN_PROGRESS : Retry (séquence §5.4)
RETRY_SCHEDULED --> FAILED : 10e tentative échouée
DELIVERED --> [*]
FAILED --> [*]
note right of PENDING
Crash pré-commit → rollback complet (INV-14)
end note
note right of FAILED
État terminal — replay crée un
nouvel événement (INV-13)
end note
note left of DELIVERED
État terminal (INV-13)
end note Diagramme de séquence — Émission événement métier (Flux D)¶
Ce diagramme couvre le flux principal : journalisation de l’intention (INV-06, INV-12), signature HMAC-SHA256 (INV-02, INV-03), validation SSRF (INV-15), et livraison avec retry.
sequenceDiagram
participant Source as Module source
participant DB as PostgreSQL
participant Worker as Worker async
participant DNS as DNS Resolver
participant Target as URL partenaire
Source->>DB: Journalise événement source (INV-12)
Source->>DB: INSERT intention livraison (INV-06)<br/>état PENDING — ACID (INV-14)
Worker->>DB: SELECT intention PENDING → IN_PROGRESS
Worker->>DB: Relit secret_sha256 (§5.3)
Worker->>Worker: Construit payload (variante A/B/C)<br/>Canonicalise JSON (§5.2)
Worker->>Worker: Signe HMAC-SHA256(secret_sha256,<br/>t + "." + payload) (INV-02, INV-03)
Worker->>DNS: Résolution A/AAAA target_url
DNS-->>Worker: IP résolue
alt IP privée / loopback / link-local / metadata (INV-15)
Worker->>DB: INSERT tentative FAILED (SSRF bloqué)
else IP autorisée
Worker->>Target: POST payload + X-ProbatioVault-Signature<br/>(IP pinning — INV-15)
alt HTTP 2xx
Target-->>Worker: 2xx
Worker->>DB: INSERT tentative DELIVERED
else Non-2xx / timeout / erreur
Target-->>Worker: erreur
Worker->>DB: INSERT tentative RETRY_SCHEDULED
Note over Worker: Retry selon séquence §5.4<br/>(max 10 tentatives → FAILED)
end
end Diagramme de séquence — Rotation du secret (Flux G, INV-09)¶
sequenceDiagram
participant Client as Client B2B
participant API as API ProbatioVault
participant DB as PostgreSQL
participant Worker as Worker async
Client->>API: POST /webhooks/{id}/rotate
API->>API: Génère nouveau secret CSPRNG ≥32 bytes
API->>API: Calcule nouveau secret_sha256
API->>DB: UPDATE secret_sha256 (ancien invalidé immédiatement)
API-->>Client: 200 + secret brut en clair (unique fois, INV-09)<br/>+ instruction SHA-256(S) pour vérification HMAC
Note over Worker: Tentatives futures relisent<br/>secret_sha256 en DB à chaque envoi
Worker->>DB: SELECT secret_sha256 (nouveau)
Worker->>Worker: Signe avec nouveau secret 6. Cas d’erreur¶
| ID | Cas | Réponse attendue |
|---|---|---|
| ERR-01 | target_url non https ou schéma interdit | 400 |
| ERR-02 | Dépassement quota webhooks/organisation (>5) | 409 Conflict |
| ERR-03 | event_type non supporté | 400 |
| ERR-04 | Accès cross-tenant | 403 |
| ERR-05 | Le partenaire répond 401 (signature absente/malformée perçue côté partenaire) | Tentative en échec + retry |
| ERR-06 | Le partenaire répond 401 (timestamp hors fenêtre anti-rejeu côté partenaire) | Tentative en échec + retry |
| ERR-07 | Réponse cible 3xx | Tentative en échec, redirection non suivie, retry |
| ERR-08 | Timeout livraison | Tentative en échec, retry |
| ERR-09 | Tentatives max atteintes | État final FAILED |
| ERR-10 | Rotation secret pendant file en attente | Tentatives futures signées avec nouveau secret uniquement |
| ERR-11 | Replay introuvable/non autorisé/non éligible | 404 (introuvable/purgé), 403 (autre org), 409 (état non éligible : PENDING/IN_PROGRESS/RETRY_SCHEDULED) |
| ERR-12 | Données source incohérentes pour la construction du payload (ex : doc_id manquant pour un événement documentaire, hash non conforme hex64) | L'intention de livraison est créée en état FAILED avec motif PAYLOAD_VALIDATION_ERROR. Aucune tentative HTTP n'est émise. L'erreur est loggée pour investigation (corruption données internes). |
| ERR-13 | SSRF détectée (IP/résolution interdite, DNS rebinding) | Blocage envoi, tentative en échec + retry si pertinent |
7. Critères d’acceptation (testables)¶
| ID | Critère | Observable |
|---|---|---|
| CA-01 | CRUD webhook organisationnel opérationnel | API create/list/update/delete dans tenant courant |
| CA-02 | Limite 5 webhooks par organisation appliquée | 6e création rejetée en 409 |
| CA-03 | Signature HMAC-SHA256 systématique et canonique | Header valide + payload signé <t>.<JSON.stringify()> |
| CA-04 | Anti-rejeu ±5 min implémenté | Hors fenêtre rejetable côté partenaire |
| CA-05 | Schéma URL strictement https | Tout autre schéma rejeté |
| CA-06 | Redirections non suivies | 3xx classé échec |
| CA-07 | Retry contractuel exact sur 10 tentatives | Séquence 1m,5m,15m,1h,1h,1h,1h,1h,1h,1h |
| CA-08 | Journal append-only métier + rétention technique >30 jours | Aucune mutation métier, purge infra conforme |
| CA-09 | Replay crée nouvel event_id et trace original_event_id côté journal | Corrélation disponible API/audit |
| CA-10 | Rotation invalide immédiatement l’ancien secret | Toute tentative post-rotation utilise nouveau secret |
| CA-11 | Isolation RLS inter-tenant stricte | Tests cross-tenant refusés |
| CA-12 | Non-mutation probatoire | États probatoires inchangés |
| CA-13 | Rate limit 100 intentions de livraison/min/org en sliding window 60s. Un événement déclenché vers N webhooks abonnés = N intentions = N unités de quota. | Excédent mis en attente (file BullMQ), non perdu, traitement dans l'ordre d'arrivée |
| CA-14 | SSRF protection à l’enregistrement + à l’envoi | IP/ranges interdits bloqués, DNS rebinding détecté |
| CA-15 | Secret HMAC conforme sécurité | CSPRNG >=32 bytes, stockage hash SHA-256, pas de cache worker |
8. Scénarios de test (Given / When / Then)¶
-
GWT-01 (Création valide) Given tenant authentifié avec 0 webhook
When création webhook HTTPS avecevent_typevalides
Then création réussie et secret non exposé en clair. -
GWT-02 (Quota dépassé) Given tenant avec 5 webhooks
When création d’un 6e
Then rejet409 Conflict. -
GWT-03 (Schéma URL interdit) Given tenant authentifié
When soumissionhttp://ouftp://
Then rejet400. -
GWT-04 (Livraison signée canonique) Given événement éligible + webhook actif
When tentative émise
Then signature calculée sur<t>.<JSON.stringify(payload)>et header valide. -
GWT-05 (2xx succès / non-2xx échec) Given endpoint partenaire
When réponse204puis401
Then204=>DELIVERED,401=> échec + retry. -
GWT-06 (Retry terminal) Given endpoint indisponible
When 10 tentatives échouent selon séquence contractuelle
Then état finalFAILEDet journal complet. -
GWT-07 (Replay) Given événement historique autorisé
When replay
Then nouvelevent_id+ lienoriginal_event_iden journal interne. -
GWT-08 (Rotation secret) Given webhook existant
When rotation
Then toutes les tentatives suivantes relisent DB et signent avec nouveau secret. -
GWT-09 (RLS) Given organisations A et B
When A accède aux ressources de B
Then refus systématique. -
GWT-10 (État terminal DELETED) Given webhook
DELETED
When tentative de transition
Then transition refusée. -
GWT-11 (SSRF + DNS rebinding) Given URL cible résolue vers IP privée ou rebinding vers IP interdite
When validation en création ou en envoi
Then blocage + aucune requête sortante vers cible interdite.
9. Hypothèses explicites¶
| ID | Hypothèse | Impact si faux |
|---|---|---|
| H-01 | Les modules producteurs journalisent l’événement source avant émission webhook. | Violation INV-12. |
| H-02 | Le JWT fournit un orgId fiable. | Rupture INV-10/INV-08. |
| H-03 | La purge technique >30 jours (cron/TTL) est disponible et auditable. | Non-conformité INV-07/CA-08. |
| H-04 | Le runtime supporte résolution DNS A/AAAA fiable avant envoi. | Risque de non-conformité INV-15. |
| H-05 | Horloge système synchronisée NTP pour anti-rejeu. | Faux positifs 401 et retries excessifs. |
10. Références¶
- Epic :
PD-186 — Backend Core - JIRA :
PD-180 - Repo :
ProbatioVault-backend - Dépendances : PD-13, PD-14, PD-15, PD-3, PD-28, PD-19, PD-31, PD-60, PD-55, PD-41