Chaindoc webhooks
Los webhooks envían datos de eventos al servidor en el momento exacto en que ocurre algo en Chaindoc. Sin polling, sin esperas. Aquí está el truco: esta guía cubre la configuración, tipos de eventos, verificación HMAC, lógica de reintentos y pruebas.
Visión general
En lugar de hacer polling constante al API, los webhooks avisan al servidor qué pasó justo cuando sucede. Los usarás para sincronizar el estado de documentos, activar flujos de trabajo después de firmas, enviar notificaciones y mantener tu base de datos actualizada.
- Entrega instantánea con hasta 3 reintentos automáticos (backoff exponencial)
- Verificación de firma HMAC SHA256 en cada payload
- Filtra únicamente los tipos de eventos que te importan
- Seguimiento del estado de entrega en tu panel
Configuración
Paso 1: Crear una API key
Navega a Configuración → Acceso API en tu panel de Chaindoc y crea una API key con la configuración de webhooks habilitada.
Paso 2: Configurar la URL del webhook
Usa el API para configurar tu endpoint de 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"
}'Paso 3: Implementar tu endpoint
Crea un endpoint en tu servidor para recibir eventos de webhook. Aquí tienes ejemplos en diferentes lenguajes:
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);Tipos de eventos
Chaindoc envía webhooks para los siguientes eventos:
document.created
Se activa cuando se crea un nuevo documento vía API.
{
"event": "document.created",
"documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
"name": "Service Agreement",
"timestamp": "2024-12-04T10:30:00.000Z"
}document.verified
Se activa cuando un documento se verifica correctamente en 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
Se activa cuando se recogen todas las firmas requeridas.
{
"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
Se activa cuando se crea una nueva solicitud de 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
Se activa cuando todos los firmantes completan sus firmas.
{
"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
Se activa cuando un firmante rechaza la solicitud de 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"
}Seguridad
Verificación de firma
Chaindoc firma todos los payloads de webhook usando HMAC SHA256. Siempre verifica las firmas para asegurar la autenticidad y prevenir ataques de repetición.
Cómo funciona la verificación
1Chaindoc crea la firmaChaindoc genera una firma HMAC usando tu secreto de webhook
2Firma enviada en el headerLa firma se envía en el header X-Webhook-Signature
3Tu servidor verificaTu servidor recalcula la firma y compara usando una función segura contra timing attacks
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');
}Lógica de reintentos
Chaindoc reintenta automáticamente las entregas fallidas de webhooks con backoff exponencial.
- 1er reintento: Después de 1 minuto
- 2do reintento: Después de 5 minutos (total: 6 minutos)
- 3er reintento: Después de 15 minutos (total: 21 minutos)
Pruebas de webhooks
Desarrollo local
Usa herramientas como ngrok para exponer tu servidor local y probar webhooks:
# Instalar ngrok
npm install -g ngrok
# Iniciar tu servidor local
node server.js
# Exponer el puerto 3000
ngrok http 3000
# Usa la URL de ngrok como endpoint de webhook
# Ejemplo: https://abc123.ngrok.io/webhooks/chaindocPruebas manuales
Prueba tu endpoint de webhook con un payload de ejemplo:
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"
}'Mejores prácticas
- Siempre verifica las firmas de webhook antes de procesar
- Responde rápido (menos de 30 segundos) para evitar timeouts
- Procesa los webhooks de forma asíncrona en una cola
- Implementa idempotencia para manejar eventos duplicados
- Registra todos los eventos de webhook para depuración y auditoría
- Monitorea los fallos de entrega de webhooks en tu panel
- Usa endpoints HTTPS por seguridad
- Maneja todos los tipos de eventos con elegancia (ignora eventos desconocidos)
Ejemplo de producción
Aquí tienes un manejador de webhooks listo para producción con integración de base de datos:
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,
verifiedAt: new Date(),
},
});
}
async function handleSignatureCompleted(payload: any) {
await prisma.signatureRequest.update({
where: { id: payload.signatureRequestId },
data: {
status: 'COMPLETED',
completedAt: new Date(),
},
});
}
async function sendNotificationEmail(payload: any) {
// Send email notification
}
function verifySignature(payload: any, signature: string, secret: string) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(JSON.stringify(payload)).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}Qué hacer a continuación
- Integración API — patrones comunes y ejemplos de flujos de trabajo
- Documentación API — referencia completa de endpoints
- SDKs — guías del Server SDK y Embed SDK
- Seguridad — verificación HMAC y gestión de claves
- Instalación — configuración del SDK para todos los frameworks