Webhooks Chaindoc
I webhooks spediscono i dati degli eventi al tuo server nel momento esatto in cui succede qualcosa su Chaindoc. Niente polling, zero attese. Questa pagina copre setup, tipi di eventi, verifica HMAC, logica di retry e testing.
Panoramica
Invece di interrogare l'API, i webhooks dicono al tuo server cosa è successo non appena accade. Li userai per sincronizzare lo stato dei documenti, attivare workflow dopo le firme, inviare notifiche e tenere aggiornato il tuo database.
- Consegna istantanea con fino a 3 retry automatici (backoff esponenziale)
- Verifica firma HMAC SHA256 su ogni payload
- Filtra solo i tipi di evento che ti interessano
- Tracciamento stato consegna nella tua dashboard
Setup
Passo 1: Crea una API key
Vai su Impostazioni → Accesso API nella tua dashboard Chaindoc e crea una API key con la configurazione webhooks abilitata.
Passo 2: Configura l'URL webhook
Usa l'API per configurare il tuo 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"
}'Passo 3: Implementa il tuo endpoint
Crea un endpoint sul tuo server per ricevere gli eventi webhook. Ecco esempi in diversi linguaggi:
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);Tipi di evento
Chaindoc invia webhooks per i seguenti eventi:
document.created
Attivato quando un nuovo documento viene creato via API.
{
"event": "document.created",
"documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
"name": "Service Agreement",
"timestamp": "2024-12-04T10:30:00.000Z"
}document.verified
Attivato quando un documento viene verificato con successo sulla 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
Attivato quando tutte le firme richieste sono state raccolte.
{
"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
Attivato quando viene creata una nuova richiesta di firma.
{
"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
Attivato quando tutti i firmatari completano le loro firme.
{
"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
Attivato quando un firmatario rifiuta la richiesta di firma.
{
"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"
}Sicurezza
Verifica firma
Chaindoc firma tutti i payload webhook usando HMAC SHA256. Verifica sempre le firme per assicurare autenticità e prevenire attacchi replay.
Come funziona la verifica
1Chaindoc crea la firmaChaindoc crea la firma HMAC usando il tuo webhook secret
2Firma inviata nell'headerLa firma viene inviata nell'header X-Webhook-Signature
3Il tuo server verificaIl tuo server ricalcola la firma e confronta usando funzione 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');
}Logica di retry
Chaindoc ritenta automaticamente le consegne webhook fallite con backoff esponenziale.
- 1° retry: Dopo 1 minuto
- 2° retry: Dopo 5 minuti (totale: 6 minuti)
- 3° retry: Dopo 15 minuti (totale: 21 minuti)
Testing webhooks
Sviluppo locale
Usa strumenti come ngrok per esporre il tuo server locale per il testing webhook:
# Installa ngrok
npm install -g ngrok
# Avvia il tuo server locale
node server.js
# Esposizione porta 3000
ngrok http 3000
# Usa l'URL ngrok come endpoint webhook
# Esempio: https://abc123.ngrok.io/webhooks/chaindocTesting manuale
Testa il tuo endpoint webhook con un payload di esempio:
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"
}'Best practice
- Verifica sempre le firme webhook prima di processare
- Rispondi rapidamente (entro 30 secondi) per evitare timeout
- Processa i webhooks in modo asincrono in una coda
- Implementa idempotenza per gestire eventi duplicati
- Logga tutti gli eventi webhook per debug e audit
- Monitora i fallimenti di consegna nella tua dashboard
- Usa endpoint HTTPS per sicurezza
- Gestisci tutti i tipi di evento (ignora quelli sconosciuti)
Esempio produzione
Ecco un gestore webhook production-ready con integrazione database:
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', txHash: payload.txHash },
});
}Cosa fare dopo
- Integrazione API — pattern comuni ed esempi di workflow
- Documentazione API — riferimento completo endpoint
- SDK — guide Server SDK e Embed SDK
- Sicurezza — verifica HMAC e gestione chiavi
- Installazione — setup SDK per tutti i framework