Chaindoc Webhooks

Recevez des notifications en temps réel quand il se passe quelque chose sur votre compte Chaindoc. Les webhooks envoient tout de suite les données d'événement à votre serveur, donc pas besoin de faire de sondage.

Aperçu

Les webhooks Chaindoc permettent à ton application de recevoir des notifications en temps réel quand des événements se produisent sur ton compte. Au lieu d'interroger l'API, les webhooks envoient les données d'événement à ton serveur dès qu'une action se produit.

Caractéristiques principales

  • Notifications en temps réel - Envoi instantané des événements à ton serveur
  • Reprises automatiques - Jusqu'à 3 tentatives de reprise avec recul exponentiel
  • Vérification de la signature - HMAC SHA256 pour l'authenticité de la charge utile
  • Filtrage des événements - Ne reçois que les événements qui t'intéressent
  • Suivi des erreurs - Surveille l'état de livraison et les échecs des webhooks.

Cas d'utilisation

  • Envoie des notifications par e-mail quand les documents sont signés.
  • Lancez des workflows quand les documents sont vérifiés sur la blockchain.
  • Mettez à jour votre base de données lorsque des demandes de signature sont créées.
  • Synchronisez le statut des documents avec les systèmes externes.
  • Piste d'audit et journalisation de conformité

Configuration

Étape 1 : Créer une clé API

Va dans Paramètres → Accès API dans ton tableau de bord Chaindoc et crée une clé API avec la configuration webhook activée.

Étape 2 : Configurer l'URL du webhook

Utilise l'API pour configurer ton point de terminaison 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 : Mettre en place le point de terminaison Webhook

Crée un point de terminaison sur ton 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é lorsqu'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

Se déclenche quand un document est validé 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.signé

Se déclenche quand toutes les signatures requises sont recueillies.

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é lorsqu'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

Se déclenche quand tous les signataires ont fini de signer.

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

Se déclenche 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 la signature

Chaindoc signe toutes les charges utiles des webhooks avec HMAC SHA256. Vérifie toujours les signatures pour t'assurer de leur authenticité et éviter les attaques par rejeu.

Comment ça marche, la vérification de signature

1Chaindoc crée une signatureChaindoc crée une signature HMAC en utilisant 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 la compare à l'aide d'une fonction sécurisée.

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 réessai

Chaindoc réessaie automatiquement les livraisons de webhooks qui ont échoué avec un délai exponentiel.

  • 1ère tentative : après 1 minute
  • Deuxième tentative : après 5 minutes (total : 6 minutes)
  • 3e tentative : après 15 minutes (total : 21 minutes)

Tester les webhooks

Développement local

Utilise des outils comme ngrok pour exposer ton serveur local pour tester les webhooks :

terminal
# Install ngrok
npm install -g ngrok

# Start your local server
node server.js

# Expose port 3000
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# Example: https://abc123.ngrok.io/webhooks/chaindoc

Tests manuels

Teste ton point de terminaison webhook avec un exemple de charge utile :

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

  • Toujours vérifier les signatures des webhooks avant de les traiter.
  • Réponds vite (en moins de 30 secondes) pour éviter les délais d'attente.
  • Traite les webhooks de manière asynchrone dans une file d'attente.
  • Mets en place l'idempotence pour gérer les événements en double.
  • Enregistre tous les événements webhook pour le débogage et l'audit.
  • Surveillez les échecs de livraison des webhooks dans votre tableau de bord.
  • Utilise des points de terminaison HTTPS pour la sécurité.
  • Gère tous les types d'événements avec élégance (ignore les événements inconnus).

Exemple complet

Voici un gestionnaire de webhook prêt à l'emploi avec intégration de 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.documentId },
    data: {
      status: 'verified',
      blockchainTxHash: payload.txHash,
      verifiedAt: new Date(payload.timestamp),
    },
  });
}

async function handleSignatureCompleted(payload: any) {
  await prisma.signatureRequest.update({
    where: { id: payload.signatureRequestId },
    data: {
      status: 'completed',
      completedAt: new Date(payload.completedAt),
    },
  });
}

function verifySignature(payload: any, signature: string, secret: string): boolean {
  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);