Веб-хуки Chaindoc

Отримуйте сповіщення в режимі реального часу, коли в вашому обліковому записі Chaindoc відбуваються події. Веб-хуки миттєво надсилають дані про події на ваш сервер, усуваючи необхідність опитування.

Огляд

Chaindoc Webhooks дозволяє вашій програмі отримувати повідомлення в режимі реального часу, коли в вашому обліковому записі відбуваються події. Замість опитування API, webhooks надсилають дані про події на ваш сервер, як тільки відбувається якась дія.

Основні функції

  • Повідомлення в режимі реального часу — миттєва доставка подій на ваш сервер
  • Автоматичні повторні спроби — до 3 спроб з експоненційним відступом
  • Перевірка підпису — HMAC SHA256 для автентичності корисного навантаження
  • Фільтрування подій — отримуйте тільки ті події, які вас цікавлять
  • Відстеження помилок — моніторинг стану доставки веб-хуків та помилок

Приклади використання

  • Надсилайте повідомлення електронною поштою, коли документи підписано
  • Запускайте робочі процеси, коли документи перевіряються в блокчейні
  • Оновлюйте базу даних при створенні запитів на підпис
  • Синхронізуйте статус документа із зовнішніми системами
  • Аудиторський слід та реєстрація відповідності

Налаштування

Крок 1: Створіть ключ API

Перейдіть до розділу «Налаштування» → «Доступ до API» на панелі інструментів Chaindoc і створіть ключ API з увімкненою конфігурацією веб-хука.

Крок 2: Налаштуйте URL-адресу веб-хука

Використовуйте API для налаштування кінцевої точки веб-хука:

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: Впровадити кінцеву точку веб-хука

Створіть кінцеву точку на своєму сервері для отримання подій веб-хука. Ось приклади на різних мовах:

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 надсилає веб-хуки для таких подій:

document.created

Спрацьовує, коли новий документ створюється через 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"
}

signature.request.rejected

Спрацьовує, коли підписувач відхиляє запит на підпис.

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. Завжди перевіряйте підписи, щоб переконатися в їх автентичності та запобігти атакам повторного відтворення.

Як працює перевірка підпису

1Chaindoc створює підписChaindoc створює підпис 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 автоматично повторює невдалі доставки веб-хуків з експоненційним відступом.

  • 1-а спроба: через 1 хвилину
  • 2-га спроба: через 5 хвилин (загалом: 6 хвилин)
  • 3-я спроба: через 15 хвилин (загалом: 21 хвилина)

Тестування веб-хуків

Місцевий розвиток

Використовуйте такі інструменти, як ngrok, щоб відкрити доступ до вашого локального сервера для тестування веб-хуків:

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

Ручне тестування

Перевірте кінцеву точку веб-хука за допомогою зразка корисного навантаження:

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

Кращі практики

  • Завжди перевіряйте підписи веб-хуків перед обробкою
  • Швидко відповідайте (менше ніж за 30 секунд), щоб уникнути тайм-аутів
  • Обробляйте веб-хуки асинхронно в черзі
  • Впровадьте ідемпотентність для обробки дублікатів подій
  • Реєструйте всі події веб-хуків для налагодження та аудиту
  • Відстежуйте помилки доставки веб-хуків у своїй панелі інструментів
  • Використовуйте кінцеві точки HTTPS для безпеки
  • Правильно обробляйте всі типи подій (ігноруйте невідомі події)

Повний приклад

Ось готовий до використання обробник веб-хуків з інтеграцією бази даних:

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