Вебхуки Chaindoc
Вебхуки надсилають дані про події на ваш сервер у той самий момент, коли щось відбувається в Chaindoc. Без опитування, без затримок. Тут розповідається про налаштування, типи подій, HMAC-верифікацію, логіку повторних спроб і тестування.
Огляд
Замість постійного опитування API, вебхуки миттєво повідомляють ваш сервер про події. Ви використовуватимете їх для синхронізації статусу документів, запуску робочих процесів після підписів, відправки сповіщень і підтримки актуальності вашої бази даних.
- Миттєва доставка з до 3 автоматичних повторних спроб (експоненціальне відкладання)
- Перевірка підпису HMAC SHA256 для кожного payload
- Фільтрація лише потрібних типів подій
- Відстеження статусу доставки в вашій панелі керування
Налаштування
Крок 1: Створення API ключа
Перейдіть до Settings → API Access у панелі керування Chaindoc і створіть API ключ з увімкненою конфігурацією вебхуків.
Крок 2: Конфігурація URL вебхука
Використовуйте API для налаштування вашого webhook endpoint:
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: Реалізація вашого endpoint
Створіть endpoint на вашому сервері для отримання 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 надсилає вебхуки для таких подій:
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 підписує всі webhook-payload за допомогою HMAC SHA256. Завжди перевіряйте підписи для забезпечення автентичності та запобігання атакам повторного відтворення.
Як працює перевірка
1Chaindoc створює підписChaindoc створює HMAC підпис за допомогою вашого webhook secret
2Підпис надсилається в заголовкуПідpis надсилається в заголовку 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 для відкриття доступу до вашого локального сервера для тестування вебхуків:
# 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 endpoint з прикладом payload:
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 секунд) для уникнення таймаутів
- Обробляйте вебхуки асинхронно в черзі
- Реалізуйте ідемпотентність для обробки дублікатів подій
- Логуйте всі webhook-події для налагодження та аудиту
- Моніторте невдалі доставки вебхуків у вашій панелі керування
- Використовуйте HTTPS endpoints для безпеки
- Обробляйте всі типи подій коректно (ігноруйте невідомі події)
Приклад для продакшену
Ось готовий до продакшену обробник вебхуків з інтеграцією бази даних:
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(),
},
});
}Що робити далі
- API інтеграція — поширені патерни та приклади робочих процесів
- API документація — повний довідник endpoint
- SDKs — гайди з Server SDK та Embed SDK
- Безпека — HMAC-верифікація та керування ключами
- Встановлення — налаштування SDK для всіх фреймворків