Chaindoc Webhooks

Nhận thông báo thời gian thực khi có sự kiện xảy ra trong tài khoản Chaindoc của cậu. Webhooks đẩy dữ liệu sự kiện đến máy chủ của cậu ngay lập tức, loại bỏ nhu cầu kiểm tra định kỳ.

Tổng quan

Chaindoc Webhooks cho phép ứng dụng của cậu nhận thông báo thời gian thực khi có sự kiện xảy ra trong tài khoản của cậu. Thay vì phải kiểm tra API, webhooks sẽ đẩy dữ liệu sự kiện đến máy chủ của cậu ngay khi có hành động xảy ra.

Tính năng chính

  • Thông báo thời gian thực - Gửi sự kiện ngay lập tức đến máy chủ của cậu
  • Tự động thử lại - Tối đa 3 lần thử lại với thời gian chờ tăng dần theo cấp số nhân
  • Xác minh chữ ký - HMAC SHA256 để đảm bảo tính xác thực của dữ liệu
  • Lọc sự kiện - Chỉ nhận các sự kiện mà cậu quan tâm
  • Theo dõi lỗi - Theo dõi trạng thái giao hàng và sự cố của webhook

Các trường hợp sử dụng

  • Gửi thông báo qua email khi tài liệu được ký kết
  • Kích hoạt quy trình làm việc khi tài liệu được xác minh trên blockchain
  • Cập nhật cơ sở dữ liệu của cậu khi có yêu cầu ký tên được tạo ra
  • Đồng bộ hóa trạng thái tài liệu với các hệ thống bên ngoài
  • Danh sách kiểm tra và ghi chép tuân thủ

Cài đặt

Bước 1: Tạo khóa API

Truy cập vào Cài đặt → Truy cập API trong bảng điều khiển Chaindoc của cậu và tạo khóa API với cấu hình webhook được kích hoạt.

Bước 2: Cấu hình URL Webhook

Sử dụng API để cấu hình điểm cuối webhook của cậu:

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

Bước 3: Triển khai điểm cuối Webhook

Tạo một điểm cuối trên máy chủ của cậu để nhận các sự kiện webhook. Dưới đây là các ví dụ bằng các ngôn ngữ khác nhau:

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

Loại sự kiện

Chaindoc gửi webhook cho các sự kiện sau:

document.created

Được kích hoạt khi một tài liệu mới được tạo thông qua API.

terminal
{
  "event": "document.created",
  "documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
  "name": "Service Agreement",
  "timestamp": "2024-12-04T10:30:00.000Z"
}

document.verified

Được kích hoạt khi tài liệu được xác minh thành công trên 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

Được kích hoạt khi tất cả các chữ ký bắt buộc đã được thu thập.

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

Được kích hoạt khi có yêu cầu chữ ký mới được tạo.

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

Được kích hoạt khi tất cả các bên ký kết hoàn thành chữ ký của mình.

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

Được kích hoạt khi người ký từ chối yêu cầu ký tên.

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

Bảo mật

Xác minh chữ ký

Chaindoc ký tất cả các payload webhook bằng HMAC SHA256. Luôn xác minh chữ ký để đảm bảo tính xác thực và ngăn chặn các cuộc tấn công tái phát.

Cách thức xác minh chữ ký hoạt động

1Chaindoc Tạo Chữ kýChaindoc tạo chữ ký HMAC bằng cách sử dụng khóa bí mật webhook của cậu.

2Chữ ký được gửi trong tiêu đềChữ ký được gửi trong tiêu đề X-Webhook-Signature

3Máy chủ của cậu xác minhMáy chủ của cậu sẽ tính lại chữ ký và so sánh bằng hàm an toàn về thời gian.

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ý luận thử lại

Chaindoc tự động thử lại việc gửi webhook thất bại với cơ chế lùi lại theo cấp số nhân.

  • Lần thử lại đầu tiên: Sau 1 phút
  • Lần thử lại thứ 2: Sau 5 phút (tổng cộng: 6 phút)
  • Lần thử thứ 3: Sau 15 phút (tổng cộng: 21 phút)

Kiểm tra Webhooks

Phát triển địa phương

Sử dụng các công cụ như ngrok để mở cổng máy chủ cục bộ của cậu cho việc kiểm thử 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

Kiểm thử thủ công

Kiểm tra điểm cuối webhook của cậu bằng một mẫu payload:

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

Thực hành tốt nhất

  • Luôn kiểm tra chữ ký webhook trước khi xử lý
  • Trả lời nhanh chóng (dưới 30 giây) để tránh bị hết thời gian chờ.
  • Xử lý webhooks một cách không đồng bộ trong hàng đợi
  • Thực hiện tính idempotency để xử lý các sự kiện trùng lặp
  • Ghi lại tất cả các sự kiện webhook để gỡ lỗi và kiểm toán
  • Theo dõi các lỗi giao hàng webhook trong bảng điều khiển của cậu
  • Sử dụng các điểm cuối HTTPS để đảm bảo an toàn.
  • Xử lý tất cả các loại sự kiện một cách linh hoạt (bỏ qua các sự kiện không xác định)

Ví dụ hoàn chỉnh

Dưới đây là trình xử lý webhook sẵn sàng cho sản xuất với tích hợp cơ sở dữ liệu:

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