Aller au contenu principal

Webhooks

Recevez des événements en temps réel quand des actions surviennent dans votre tenant. Signature HMAC-SHA256 pour l'authenticité.

Comment ça fonctionne

  1. Enregistrez votre endpoint HTTPS via POST /webhooks
  2. QualiForma stocke un secret unique (whsec_...) pour ce webhook
  3. Quand un événement survient, QualiForma POSTe le payload JSON sur votre URL
  4. Vérifiez la signature X-Qualiforma-Signature avec le secret HMAC-SHA256
  5. Répondez 200 OK en moins de 30 secondes (timeout)
  6. 3 retries automatiques en cas d'échec (1min, 5min, 30min)

POST/webhooks

Enregistre un endpoint et retourne le secret HMAC. Stocker ce secret immédiatement— il n'est plus accessible ensuite.

Enregistrer un webhook · cURL / Shell
TOKEN="eyJhbGci..."

curl -X POST https://api.qualiforma.site/api/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H 'X-Tenant-ID: votre-tenant' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://votre-app.com/webhooks/qualiforma",
    "events": [
      "enrollment.created",
      "enrollment.completed",
      "payment.completed",
      "course.published"
    ],
    "description": "Webhook principal production"
  }'

# Réponse :
# {
#   "data": {
#     "id": "wh_abc123",
#     "url": "https://votre-app.com/webhooks/qualiforma",
#     "secret": "whsec_XYZ...",  ← à stocker dans votre secret manager
#     "events": ["enrollment.created", "..."],
#     "active": true
#   }
# }

Format du payload

Chaque événement est envoyé en POST avec le header X-Qualiforma-Signature: {hmac_hex}.

Exemple — enrollment.created · JSON
{
  "id": "evt_abc123",
  "event": "enrollment.created",
  "tenantId": "votre-tenant",
  "createdAt": "2026-01-01T12:30:00.000Z",
  "data": {
    "enrollmentId": "enr_def456",
    "courseId": "crs_ghi789",
    "userId": "usr_jkl012",
    "courseTitle": "Excel Avancé pour RH",
    "userName": "Marie Martin",
    "enrolledAt": "2026-01-01T12:30:00.000Z"
  }
}

Vérification HMAC-SHA256

La signature est calculée sur le raw body bytes (avant tout parsing JSON). Utiliser hmac.compare_digest / timingSafeEqual pour éviter les timing attacks.

Vérification HMAC — Python/Flask · Python
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = 'whsec_XYZ...'  # depuis votre secret manager

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    """Vérifie la signature HMAC-SHA256 du webhook QualiForma."""
    expected = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route('/webhooks/qualiforma', methods=['POST'])
def handle_webhook():
    payload = request.get_data()  # raw bytes — NE PAS parser avant vérification
    signature = request.headers.get('X-Qualiforma-Signature', '')

    if not verify_webhook(payload, signature, WEBHOOK_SECRET):
        abort(401, 'Signature invalide')

    event = request.json
    handle_event(event)
    return '', 200

def handle_event(event: dict):
    match event['event']:
        case 'enrollment.created':
            # Inscrire l'apprenant dans votre SIRH
            pass
        case 'payment.completed':
            # Émettre une facture
            pass
        case 'enrollment.completed':
            # Générer un certificat externe
            pass

Événements disponibles (14)

Liste des événements webhook disponibles
ÉvénementDescription
enrollment.createdUn apprenant vient d'être inscrit à une formation
enrollment.completedUn apprenant a terminé une formation (100% progression)
enrollment.suspendedAccès suspendu manuellement par un admin
enrollment.revokedInscription révoquée (remboursement ou admin)
payment.completedPaiement capturé avec succès
payment.refundedPaiement remboursé (partiel ou total)
payment.failedPaiement échoué ou annulé par l'apprenant
course.publishedFormation publiée (DRAFT → PUBLISHED)
course.archivedFormation archivée
session.startedSession live démarrée
session.endedSession live terminée
emargement.signedEmargement signé par un apprenant
certificate.generatedAttestation de formation générée
user.createdNouvel utilisateur créé dans le tenant

Bonnes pratiques

  • Répondez 200 immédiatement, traitez l'événement en arrière-plan (queue)
  • Rendez votre handler idempotent — QualiForma peut envoyer un événement plusieurs fois
  • Vérifiez toujours la signature avant tout traitement
  • Stockez le secret whsec_ dans un secret manager (jamais en clair dans le code)
  • Loggez les événements reçus pour le debugging (sans les données sensibles)