Webhook Chaindoc
Ricevi notifiche in tempo reale quando succede qualcosa nel tuo account Chaindoc. I webhook mandano subito i dati degli eventi al tuo server, così non devi più fare il polling.
Panoramica
I webhook di Chaindoc permettono alla tua app di ricevere notifiche in tempo reale quando succede qualcosa nel tuo account. Invece di interrogare l'API, i webhook mandano i dati degli eventi al tuo server appena succede qualcosa.
Caratteristiche principali
- Notifiche in tempo reale: invio istantaneo degli eventi al tuo server
- Ripetizioni automatiche: fino a 3 tentativi con backoff esponenziale
- Verifica della firma - HMAC SHA256 per l'autenticità del payload
- Filtraggio degli eventi: ricevi solo gli eventi che ti interessano
- Monitoraggio degli errori: controlla lo stato di consegna dei webhook e gli errori
Casi d'uso
- Manda notifiche via e-mail quando i documenti vengono firmati
- Attiva i flussi di lavoro quando i documenti vengono verificati sulla blockchain
- Aggiorna il tuo database quando vengono create richieste di firma
- Sincronizza lo stato del documento con i sistemi esterni
- Audit trail e registrazione della conformità
Configurazione
Passaggio 1: crea una chiave API
Vai su Impostazioni → Accesso API nella tua dashboard Chaindoc e crea una chiave API con la configurazione webhook abilitata.
Passaggio 2: configurare l'URL del 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"
}'Passaggio 3: implementare l'endpoint Webhook
Crea un endpoint sul tuo server per ricevere gli eventi webhook. Ecco alcuni 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 eventi
Chaindoc manda dei webhook per questi eventi:
document.created
Si attiva quando si crea un nuovo documento tramite API.
{
"event": "document.created",
"documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
"name": "Service Agreement",
"timestamp": "2024-12-04T10:30:00.000Z"
}document.verified
Si attiva 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
Si attiva quando hai raccolto tutte le firme necessarie.
{
"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
Si attiva 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
Si attiva quando tutti i firmatari hanno finito di firmare.
{
"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"
}firma.richiesta.rifiutata
Si attiva quando qualcuno 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 della firma
Chaindoc firma tutti i payload dei webhook usando HMAC SHA256. Controlla sempre le firme per assicurarti che siano autentiche e per evitare attacchi di replay.
Come funziona la verifica della firma
1Chaindoc crea una firmaChaindoc crea una firma HMAC usando il tuo segreto webhook
2Firma inviata nell'intestazioneLa firma viene inviata nell'intestazione X-Webhook-Signature.
3Il tuo server verificaIl tuo server ricalcola la firma e la confronta usando una funzione sicura dal punto di vista temporale.
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 riprova
Chaindoc riprova automaticamente a inviare i webhook che non sono andati a buon fine con un backoff esponenziale.
- 1° tentativo: dopo 1 minuto
- Secondo tentativo: dopo 5 minuti (totale: 6 minuti)
- Terzo tentativo: dopo 15 minuti (totale: 21 minuti)
Testare i webhook
Sviluppo locale
Usa strumenti come ngrok per rendere visibile il tuo server locale per testare i webhook:
# 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/chaindocTest manuali
Prova 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"
}'Buone pratiche
- Controlla sempre le firme dei webhook prima di elaborarli.
- Rispondi velocemente (entro 30 secondi) per evitare che scada il tempo
- Elabora i webhook in modo asincrono in una coda
- Usa l'idempotenza per gestire gli eventi duplicati
- Registra tutti gli eventi webhook per il debug e l'audit
- Tieni d'occhio gli errori di consegna dei webhook nella tua dashboard.
- Usa endpoint HTTPS per la sicurezza
- Gestisci tutti i tipi di eventi con eleganza (ignora quelli che non conosci)
Esempio completo
Ecco un gestore di webhook pronto per la produzione con integrazione del 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',
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);