Webhooks API

Les webhooks vous permettent de recevoir des notifications en temps réel lorsque des événements se produisent dans DocFlow.

ℹ️ Les webhooks sont disponibles uniquement pour les plans Pro et Business.

Événements disponibles

ÉvénementDescription
render.completedUn render a été généré avec succès
render.failedUn render a échoué

Endpoints

MéthodeEndpointDescription
POST/webhooksCréer un webhook
GET/webhooksLister les webhooks
PATCH/webhooks/:idModifier un webhook
DELETE/webhooks/:idSupprimer un webhook
POST/webhooks/:id/rotate-secretRenouveler le secret

Créer un webhook

POST /webhooks

Request Body

{
  "url": "https://api.exemple.com/webhooks/docflow",
  "events": ["render.completed", "render.failed"]
}
ParamètreTypeRequisDescription
urlStringOuiURL HTTPS qui recevra les événements
eventsString[]OuiListe des événements à écouter

Response (201 Created)

{
  "data": {
    "id": "whk_abc123",
    "url": "https://api.exemple.com/webhooks/docflow",
    "events": ["render.completed", "render.failed"],
    "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx",
    "isActive": true,
    "failCount": 0,
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

⚠️ Le secret (whsec_...) n'est affiché qu'une seule fois. Stockez-le en sécurité pour vérifier les signatures des webhooks.


Format des webhooks

Lorsqu'un événement se produit, DocFlow envoie une requête POST à votre URL avec le payload suivant :

Headers

Content-Type: application/json
X-DocFlow-Event: render.completed
X-DocFlow-Signature: sha256=xxxxxxxxxxxxxxxxxxxxxxxx
X-DocFlow-Timestamp: 1705316400

Payload pour render.completed

{
  "event": "render.completed",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "renderId": "rnd_xyz789",
    "templateId": "tpl_abc123",
    "templateName": "Contrat de travail",
    "status": "completed",
    "downloadUrl": "https://storage.docflow.io/renders/rnd_xyz789.pdf?token=...",
    "fileSize": 256000,
    "durationMs": 342,
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

Payload pour render.failed

{
  "event": "render.failed",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "renderId": "rnd_xyz789",
    "templateId": "tpl_abc123",
    "templateName": "Contrat de travail",
    "status": "failed",
    "errorCode": "RENDER_FAILED",
    "errorMsg": "Failed to render PDF: corrupted template file",
    "createdAt": "2024-01-15T10:30:00Z"
  }
}

Vérification de signature

Pour vous assurer qu'un webhook provient bien de DocFlow, vérifiez la signature HMAC-SHA256.

Algorithme

  1. Récupérez le timestamp (X-DocFlow-Timestamp) et la signature (X-DocFlow-Signature)
  2. Construisez le message : {timestamp}.{body}
  3. Calculez le HMAC-SHA256 avec votre secret
  4. Comparez avec la signature reçue

Exemple Node.js

import crypto from 'crypto';

function verifyWebhook(payload, signature, timestamp, secret) {
  const message = `${timestamp}.${payload}`;
  const expectedSignature = 'sha256=' +
    crypto.createHmac('sha256', secret)
      .update(message)
      .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Utilisation
const isValid = verifyWebhook(
  req.body,
  req.headers['x-docflow-signature'],
  req.headers['x-docflow-timestamp'],
  process.env.DOCFLOW_WEBHOOK_SECRET
);

if (!isValid) {
  return res.status(401).send('Invalid signature');
}

Exemple Python

import hmac
import hashlib

def verify_webhook(payload, signature, timestamp, secret):
    message = f"{timestamp}.{payload}"
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Réponse attendue

Votre endpoint doit retourner un code HTTP 2xx dans les 30 secondes. Tout autre code sera considéré comme un échec.

Politique de retry

En cas d'échec, DocFlow réessaie automatiquement jusqu'à 3 fois avec un délai exponentiel :

  • 1ère tentative : immédiate
  • 2ème tentative : après 1 minute
  • 3ème tentative : après 5 minutes
  • 4ème tentative : après 30 minutes

Après 5 échecs consécutifs, le webhook est automatiquement désactivé. Vous recevrez un email de notification.


Renouveler le secret

POST /webhooks/:id/rotate-secret

Génère un nouveau secret pour le webhook. L'ancien secret est immédiatement invalidé.

Response

{
  "data": {
    "id": "whk_abc123",
    "secret": "whsec_yyyyyyyyyyyyyyyyyyyy",
    ...
  }
}

Bonnes pratiques

  • Toujours vérifier la signature avant de traiter un webhook
  • Vérifier que le timestamp n'est pas trop ancien (protection contre les replay attacks)
  • Traiter les webhooks de façon idempotente (un même événement peut être reçu plusieurs fois)
  • Répondre rapidement (moins de 30 secondes) et traiter en arrière-plan si nécessaire
  • Logger les webhooks reçus pour le debugging