Веб-хуки Chaindoc
Получайте уведомления в реальном времени, когда в вашем аккаунте Chaindoc что-то происходит. Webhooks сразу отправляют данные о событиях на ваш сервер, так что не нужно ничего проверять.
Обзор
Веб-хуки Chaindoc позволяют вашему приложению получать уведомления в реальном времени, когда в вашей учетной записи что-то происходит. Вместо того, чтобы опрашивать API, веб-хуки отправляют данные о событиях на ваш сервер, как только что-то происходит.
Основные особенности
- Уведомления в реальном времени — мгновенная доставка событий на ваш сервер
- Автоматические повторные попытки — до 3 попыток с экспоненциальным отступом
- Проверка подписи — HMAC SHA256 для проверки подлинности данных
- Фильтрация событий — получайте только те события, которые вам интересны
- Отслеживание ошибок — следи за тем, как работают веб-хуки и когда что-то не так
Примеры использования
- Отправляйте уведомления по электронной почте, когда документы подписаны
- Запускайте рабочие процессы, когда документы проверяются в блокчейне
- Обновляйте базу данных, когда появляются запросы на подпись
- Синхронизируйте статус документа с внешними системами
- Контрольный журнал и регистрация соответствия
Настройка
Шаг 1: Создайте ключ API
Зайдите в «Настройки» → «Доступ к API» в панели управления Chaindoc и создайте ключ API с включенной настройкой веб-хука.
Шаг 2: Настрой URL веб-хука
Используйте API, чтобы настроить конечную точку веб-хука:
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 создаётся новый документ.
{
"event": "document.created",
"documentId": "86840ee4-8bf2-4a91-a289-e99d8307ec25",
"name": "Service Agreement",
"timestamp": "2024-12-04T10:30:00.000Z"
}document.verified
Срабатывает, когда документ успешно проверяется в блокчейне.
{
"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
Срабатывает, когда все нужные подписи собраны.
{
"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
Срабатывает, когда появляется новый запрос на подпись.
{
"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
Срабатывает, когда все подписавшиеся ставят свои подписи.
{
"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
Срабатывает, когда подписант отклоняет запрос на подпись.
{
"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 минуту
- Вторая попытка: через 5 минут (всего: 6 минут)
- Третья попытка: через 15 минут (всего: 21 минута)
Тестирование веб-хуков
Локальная разработка
Используйте такие инструменты, как ngrok, чтобы открыть доступ к вашему локальному серверу для тестирования веб-хуков:
# 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Ручное тестирование
Протестируйте конечную точку веб-хука с помощью образца полезной нагрузки:
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-конечные точки для безопасности
- Обрабатывайте все типы событий аккуратно (не обращайте внимания на неизвестные события)
Полный пример
Вот готовый к использованию обработчик веб-хуков с интеграцией в базу данных:
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);