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:

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"
  }'

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.

terminal
{
  "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.

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

Attivato quando tutte le firme richieste sono state raccolte.

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

Attivato quando viene creata una nuova richiesta di firma.

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

Attivato quando tutti i firmatari completano le loro firme.

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

Attivato quando un firmatario rifiuta la richiesta di firma.

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"
}

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:

terminal
# 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/chaindoc

Testing manuale

Testa il tuo endpoint webhook con un payload di esempio:

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"
  }'

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:

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', txHash: payload.txHash },
  });
}

Cosa fare dopo