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:

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

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.

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

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

Si attiva quando hai raccolto tutte le firme necessarie.

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

Si attiva 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

Si attiva quando tutti i firmatari hanno finito di firmare.

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

firma.richiesta.rifiutata

Si attiva quando qualcuno 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 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:

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

Test manuali

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

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:

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);