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 :
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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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 :
# 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/chaindocTests manuels
Testez votre endpoint webhook avec un payload exemple :
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 :
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.documenCe qu'il faut retenir
- Intégration API — patterns communs et exemples de workflows
- Documentation API — référence complète des endpoints
- SDKs — guides Server SDK et Embed SDK
- Sécurité — vérification HMAC et gestion des clés
- Installation — configuration SDK pour tous les frameworks