Webhooks Chaindoc

Les webhooks poussent les données d'événement vers votre serveur instantanément. Pas de polling, pas de délais. Voici tout ce qu'il faut savoir : configuration, types d'événements, vérification HMAC, logique de retry et tests.

Vue d'ensemble

Au lieu de poller l'API, les webhooks informent votre serveur dès qu'un événement se produit. Ça sert à synchroniser le statut des documents, déclencher des workflows après des signatures, envoyer des notifications et garder votre base de données à jour.

  • Livraison instantanée avec jusqu'à 3 retries automatiques (exponential backoff)
  • Vérification de signature HMAC SHA256 sur chaque payload
  • Filtrage sur uniquement les types d'événements qui vous intéressent
  • Suivi du statut de livraison dans votre dashboard

Configuration

Étape 1 : Créer une clé API

Allez dans Paramètres → Accès API dans votre dashboard Chaindoc et créez une clé API avec la configuration webhook activée.

Étape 2 : Configurer l'URL webhook

Utilisez l'API pour configurer votre endpoint webhook :

terminal
curl -X PATCH https://api.chaindoc.io/user/api-access/1/config \
  -H "Authorization: Bearer your_auth_token" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://yourapp.com/webhooks/chaindoc",
    "webhookEnabled": true,
    "webhookSecret": "your_secure_random_string"
  }'

Étape 3 : Implémenter votre endpoint

Créez un endpoint sur votre serveur pour recevoir les événements webhook. Voici des exemples dans différents langages :

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

app.post('/webhooks/chaindoc', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const eventType = req.headers['x-webhook-event'];
  
  // Verify signature
  if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process event
  console.log('Received event:', eventType, req.body);
  
  // Handle different event types
  switch (eventType) {
    case 'document.created':
      handleDocumentCreated(req.body);
      break;
    case 'document.verified':
      handleDocumentVerified(req.body);
      break;
    case 'signature.request.completed':
      handleSignatureCompleted(req.body);
      break;
  }
  
  // Always respond with 200 OK
  res.status(200).send('Webhook received');
});

function verifySignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(JSON.stringify(payload)).digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

app.listen(3000);

Types d'événements

Chaindoc envoie des webhooks pour les événements suivants :

document.created

Déclenché quand un nouveau document est créé via l'API.

terminal
{
  "event": "document.created",
  "documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
  "name": "Service Agreement",
  "timestamp": "2024-12-04T10:30:00.000Z"
}

document.verified

Déclenché quand un document est vérifié avec succès sur la blockchain.

terminal
{
  "event": "document.verified",
  "documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
  "versionId": "f0b7721f-0399-4035-9b69-7b95d3a367f0",
  "txHash": "0x789ghi...",
  "chainId": 137,
  "timestamp": "2024-12-04T10:35:00.000Z"
}

document.signed

Déclenché quand toutes les signatures requises sont collectées.

terminal
{
  "event": "document.signed",
  "documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
  "signatureRequestId": "req_21096b94498f4a2d9795e810edc2c9a9",
  "signers": [
    {
      "email": "signer1@example.com",
      "signedAt": "2024-12-04T10:30:00.000Z"
    },
    {
      "email": "signer2@example.com",
      "signedAt": "2024-12-04T10:32:00.000Z"
    }
  ],
  "timestamp": "2024-12-04T10:32:00.000Z"
}

signature.request.created

Déclenché quand une nouvelle demande de signature est créée.

terminal
{
  "event": "signature.request.created",
  "signatureRequestId": "req_21096b94498f4a2d9795e810edc2c9a9",
  "documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
  "recipients": [
    {"email": "signer1@example.com"},
    {"email": "signer2@example.com"}
  ],
  "deadline": "2024-12-31T23:59:59.000Z",
  "timestamp": "2024-12-04T10:30:00.000Z"
}

signature.request.completed

Déclenché quand tous les signataires complètent leurs signatures.

terminal
{
  "event": "signature.request.completed",
  "signatureRequestId": "req_21096b94498f4a2d9795e810edc2c9a9",
  "documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
  "completedAt": "2024-12-04T10:32:00.000Z",
  "timestamp": "2024-12-04T10:32:00.000Z"
}

signature.request.rejected

Déclenché quand un signataire refuse la demande de signature.

terminal
{
  "event": "signature.request.rejected",
  "signatureRequestId": "req_21096b94498f4a2d9795e810edc2c9a9",
  "documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
  "rejectedBy": "signer1@example.com",
  "reason": "Terms not acceptable",
  "timestamp": "2024-12-04T10:30:00.000Z"
}

Sécurité

Vérification de signature

Chaindoc signe tous les payloads webhook avec HMAC SHA256. Vérifiez toujours les signatures pour garantir l'authenticité et éviter les attaques par replay.

Comment fonctionne la vérification

1Chaindoc crée la signatureChaindoc crée une signature HMAC avec votre secret webhook

2Signature envoyée dans l'en-têteLa signature est envoyée dans l'en-tête X-Webhook-Signature

3Votre serveur vérifieVotre serveur recalcule la signature et compare avec une fonction timing-safe

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(JSON.stringify(payload)).digest('hex');
  
  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

// Usage
const isValid = verifyWebhookSignature(
  req.body,
  req.headers['x-webhook-signature'],
  process.env.WEBHOOK_SECRET
);

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

Logique de retry

Chaindoc retry automatiquement les livraisons webhook échouées avec un exponential backoff.

  • 1er retry : Après 1 minute
  • 2ème retry : Après 5 minutes (total : 6 minutes)
  • 3ème retry : Après 15 minutes (total : 21 minutes)

Tester les webhooks

Développement local

Utilisez des outils comme ngrok pour exposer votre serveur local pendant les tests :

terminal
# Installer ngrok
npm install -g ngrok

# Démarrer votre serveur local
node server.js

# Exposer le port 3000
ngrok http 3000

# Utilisez l'URL ngrok comme endpoint webhook
# Exemple : https://abc123.ngrok.io/webhooks/chaindoc

Tests manuels

Testez votre endpoint webhook avec un payload exemple :

terminal
curl -X POST https://yourapp.com/webhooks/chaindoc \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Event: document.created" \
  -H "X-Webhook-Signature: test_signature" \
  -d '{
    "event": "document.created",
    "documentId": "test-123",
    "name": "Test Document",
    "timestamp": "2024-12-04T10:30:00.000Z"
  }'

Bonnes pratiques

  • Vérifiez toujours les signatures webhook avant de traiter
  • Répondez rapidement (moins de 30 secondes) pour éviter les timeouts
  • Traitez les webhooks de manière asynchrone dans une queue
  • Implémentez l'idempotence pour gérer les événements dupliqués
  • Loggez tous les événements webhook pour debug et audit
  • Surveillez les échecs de livraison dans votre dashboard
  • Utilisez des endpoints HTTPS pour la sécurité
  • Gérez tous les types d'événements proprement (ignorez les inconnus)

Exemple production

Voici un handler webhook prêt pour la production avec intégration base de données :

webhooks/chaindoc.ts
import express from 'express';
import crypto from 'crypto';
import { PrismaClient } from '@prisma/client';

const app = express();
const prisma = new PrismaClient();

app.use(express.json());

app.post('/webhooks/chaindoc', async (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const eventType = req.headers['x-webhook-event'] as string;
  const payload = req.body;
  
  // 1. Verify signature
  if (!verifySignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
    console.error('Invalid webhook signature');
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // 2. Check for duplicate events (idempotency)
  const eventId = `${eventType}-${payload.timestamp}`;
  const existing = await prisma.webhookEvent.findUnique({
    where: { eventId },
  });
  
  if (existing) {
    console.log('Duplicate event, skipping:', eventId);
    return res.status(200).json({ status: 'duplicate' });
  }
  
  // 3. Store event
  await prisma.webhookEvent.create({
    data: {
      eventId,
      eventType,
      payload,
      processedAt: new Date(),
    },
  });
  
  // 4. Process event asynchronously
  processWebhookAsync(eventType, payload).catch((error) => {
    console.error('Error processing webhook:', error);
  });
  
  // 5. Respond immediately
  res.status(200).json({ status: 'received' });
});

async function processWebhookAsync(eventType: string, payload: any) {
  switch (eventType) {
    case 'document.verified':
      await handleDocumentVerified(payload);
      break;
    case 'signature.request.completed':
      await handleSignatureCompleted(payload);
      await sendNotificationEmail(payload);
      break;
    case 'signature.request.rejected':
      await handleSignatureRejected(payload);
      break;
  }
}

async function handleDocumentVerified(payload: any) {
  await prisma.document.update({
    where: { id: payload.documen

Ce qu'il faut retenir