Chaindoc Webhooks
当您的Chaindoc账户发生事件时,实时接收通知。Webhooks将事件数据即时推送至您的服务器,无需轮询。
要求
商业版订阅(仅商业版用户可配置 webhook) 服务器上的HTTPS端点 公开可访问的URL(Webhook无法发送至本地主机)
概述
Chaindoc Webhooks 允许您的应用在账户发生事件时接收实时通知。无需轮询 API,一旦操作发生,Webhooks 便会立即将事件数据推送至您的服务器。
主要功能
- 实时通知 - 即时将事件推送至您的服务器
- 自动重试 - 支持最多3次重试,采用指数级后退机制
- 签名验证 - 采用HMAC SHA256确保有效负载真实性
- 事件过滤 - 仅接收您关注的事件
- 错误追踪 - 监控Webhook发送状态及失败情况
使用场景
- 当文件签署时发送邮件通知
- 当文件在区块链上完成验证时触发工作流
- 当签名请求创建时更新您的数据库
- 与外部系统同步文档状态
- 审计追踪与合规日志记录
设置
步骤1:创建API密钥
在 Chaindoc 仪表盘中导航至“设置 → API 访问”,创建一个启用 webhook 配置的 API 密钥。
步骤 2:配置 Webhook URL
使用API配置您的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"
}'步骤3:实现Webhook端点
在服务器上创建一个端点以接收 webhook 事件。以下是不同语言的示例:
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);活动类型
Chaindoc会在以下事件发生时发送webhook:
文档创建时间
当通过API创建新文档时触发。
terminal
{
"event": "document.created",
"documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
"name": "Service Agreement",
"timestamp": "2024-12-04T10:30:00.000Z"
}document.verified
当文档在区块链上成功验证时触发。
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
当所有必需签名收集完毕时触发。
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
当创建新的签名请求时触发。
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
当所有签署人完成签名时触发。
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"
}签名请求被拒绝
当签名者拒绝签名请求时触发。
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"
}安全
签名验证
Chaindoc使用HMAC SHA256对所有webhook有效负载进行签名。请务必验证签名以确保真实性并防止重放攻击。
安全最佳实践
始终使用时序安全的比较函数 生产环境中绝不跳过签名验证 请妥善保管您的Webhook密钥 定期轮换密钥 仅使用HTTPS接口
签名验证机制说明
1Chaindoc 生成签名Chaindoc使用您的webhook密钥生成HMAC签名
2签名已发送至标题签名通过X-Webhook-Signature标头发送
3您的服务器验证您的服务器将重新计算签名,并使用时序安全函数进行比对
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');
}重试逻辑
Chaindoc会自动对失败的webhook发送进行指数级后退重试。
- 首次重试:1分钟后
- 第二次重试:5分钟后(总计:6分钟)
- 第三次重试:15分钟后(累计:21分钟)
成功标准
您的端点返回HTTP状态码200-299 您的端点将在30秒内响应 传输过程中未发生网络错误
测试 Webhooks
本地开发
使用ngrok等工具将本地服务器暴露出来进行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手动测试
使用示例有效负载测试您的 webhook 端点:
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"
}'最佳实践
- 处理前务必验证Webhook签名
- 请快速响应(30秒内)以避免超时
- 在队列中异步处理Webhook
- 实现幂等性以处理重复事件
- 记录所有Webhook事件以供调试和审计
- 在控制台中监控 webhook 发送失败情况
- 为保障安全,请使用HTTPS接口
- 优雅处理所有事件类型(忽略未知事件)
完整示例
以下是一个具备数据库集成的生产就绪型Webhook处理程序:
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);后续步骤
在 Chaindoc 控制台中配置您的 webhook URL 开发期间使用ngrok进行测试 在日志中监控 webhook 交付情况 设置未成功交付的提醒