Concepts

Webhooks — push events vers tes apps tierces

Stripe-style. Tu déclares une URL + une liste d'events qui t'intéressent, ProofIt te POST le JSON HMAC-signé dès qu'une session change d'état. Pas de polling, pas de WebSocket. Retry exponentiel et DLQ après épuisement.

Cas d'usage

  • Notifier ton SI métier dès que la session est validée (ex : libérer un dossier RH, déclencher un virement)
  • Archiver le session_root_hash dans ton data lake au moment du session.closed
  • Téléversement automatique du bundle dès bundle.generated — le payload porte anchor_coverage : n'archiver que complete (jamais un provisoire)
  • Devenir témoin de sa propre chaîne via checkpoint.created : vérifier la consistance puis contre-signer avec SA clé Ed25519 — rend toute réécriture détectable
  • Re-router vers Slack/Teams pour les refus (session.refused) avec la reason

Événements supportés

Event Émis quand
session.openedPOST /v1/sessions accepté
session.validatedOTP correct, session passe en OPEN
session.plan_committedManifest prévisionnel scellé à l'OTP (si planned_operations fourni)
session.signing_context_attestedContexte de signature attesté (si signing_context fourni)
session.action_recordedVALIDATION_ACTION écrite dans la chaîne
session.artifact_attachedPDF/cert signature attaché
session.refusedRefus explicite ou OTP max attempts
session.plan_resolvedVerdict du manifest calculé à la clôture (MATCH/PARTIAL/DIVERGENT/EMPTY)
session.closedCloseSession + session_root_hash calculé
session.abandonedTimeout détecté par app:sessions:detect-abandoned
bundle.generatedASiC-E construit (manuel ou auto via cron)
anchor.confirmedAncrage confirmé (jeton TSP qualifié reçu ou OTS Bitcoin upgradé)
checkpoint.createdTête de chaîne signée émise — le payload porte le canonical exact à contre-signer (témoin, couche 4)
anchor.failedTous les fournisseurs d'ancrage ont échoué pour ce lot

Format du payload

{
  "id": "01938a7a-1f6c-7c00-b1f0-...",        // UUID v7, idempotency key
  "type": "session.closed",
  "occurred_at": "2026-05-22T14:32:01+02:00",
  "organisation_id": "019e4f7f-...",
  "data": {
    "session_id": "...",
    "actor_user_id": "...",
    "closed_at": "...",
    "session_root_hash": "abc123..."
  }
}

Vérifier la signature

Chaque POST inclut ces headers :

  • X-Timestamp : unix timestamp serveur ProofIt
  • X-Signature-SHA256 : hmac_sha256(secret, "{timestamp}.{raw_body}")
  • X-Signature-Version : v1 (schéma de signature, pour migration future)
  • X-ProofIt-Event : le type d'event (ex. session.closed)
  • X-ProofIt-Event-Id : UUID v7 idempotency — refuse les doublons côté receveur
D'où vient le secret ? C'est le secret HMAC de callback de ton organisation, affiché une seule fois à l'inscription (/register) à côté de ta clé API. Tu peux le régénérer depuis ton espace admin (action « Régénérer le secret de callback ») — les anciens secrets cessent alors immédiatement de fonctionner, pense à mettre à jour ton serveur avant.

Côté organisation tu dois :

  1. Rejeter si |now - X-Timestamp| > 300s (replay defense)
  2. Recalculer la signature avec ton secret stocké
  3. Comparer en timing-safe (hash_equals en PHP, crypto.timingSafeEqual en Node)
  4. Si X-ProofIt-Event-Id déjà vu → 200 OK sans rejouer la logique métier

Retry & DLQ

  • 2xx → SUCCESS, plus de retry
  • 5xx, 408, 425, 429, timeout → RETRYING, jusqu'à 5 tentatives (backoff exponentiel ×3, max 1h)
  • 4xx (sauf timing) → DLQ direct (config receveur fautive, retry inutile)
  • Épuisement → DLQ + last_status_code sur la subscription

API CRUD

Endpoint Scope
POST /v1/webhook-subscriptionswebhooks:write
GET /v1/webhook-subscriptionswebhooks:read
GET /v1/webhook-subscriptions/{id}webhooks:read
PATCH /v1/webhook-subscriptions/{id} (pause/resume/disable)webhooks:write
DELETE /v1/webhook-subscriptions/{id}webhooks:write
GET /v1/webhook-subscriptions/{id}/deliverieswebhooks:read