Chaindoc webhooks

Webhooks, Chaindoc'ta bir şey olduğu anda olay verilerini sunucunuza iletir. Polling yok, gecikme yok. Bu sayfada kurulum, olay türleri, HMAC doğrulama, yeniden deneme mantığı ve test adımlarını bulacaksınız.

Genel bakış

API'yi polling yapmak yerine, webhooks sunucunuza bir şey olduğunda hemen haber verir. Belge durumunu senkronize etmek, imza sonrası iş akışlarını tetiklemek, bildirim göndermek ve veritabanınızı güncel tutmak için kullanacaksınız.

  • Üstel geri çekilme ile 3 otomatik yeniden denemeye kadar anlık teslimat
  • Her payload'da HMAC SHA256 imza doğrulaması
  • Yalnızca ilgilendiğiniz olay türlerini filtreleme
  • Dashboard'da teslimat durumu takibi

Kurulum

Adım 1: API anahtarı oluşturun

Chaindoc dashboard'ınızda Ayarlar → API Erişimi'ne gidin ve webhook yapılandırması etkinleştirilmiş bir API anahtarı oluşturun.

Adım 2: Webhook URL'sini yapılandırın

Webhook endpoint'inizi yapılandırmak için API'yi kullanın:

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

Adım 3: Endpoint'inizi uygulayın

Webhook olaylarını almak için sunucunuzda bir endpoint oluşturun. İşte farklı dillerde örnekler:

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

Olay türleri

Chaindoc aşağıdaki olaylar için webhook gönderir:

document.created

API aracılığıyla yeni bir belge oluşturulduğunda tetiklenir.

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

document.verified

Bir belge blockchain'de başarıyla doğrulandığında tetiklenir.

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

Tüm gerekli imzalar toplandığında tetiklenir.

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

Yeni bir imza isteği oluşturulduğunda tetiklenir.

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

Tüm imzacılar imzalarını tamamladığında tetiklenir.

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

Bir imzacı imza isteğini reddettiğinde tetiklenir.

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

Güvenlik

İmza doğrulama

Chaindoc tüm webhook payload'larını HMAC SHA256 kullanarak imzalar. Orijinalliği sağlamak ve tekrar saldırılarını önlemek için her zaman imzaları doğrulayın.

Doğrulama nasıl çalışır

1Chaindoc imzayı oluştururChaindoc webhook secret'ınızı kullanarak HMAC imzası oluşturur

2İmza header'da gönderilirİmza X-Webhook-Signature header'ında gönderilir

3Sunucunuz doğrularSunucunuz imzayı yeniden hesaplar ve timing-safe fonksiyonla karşılaştırır

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

Yeniden deneme mantığı

Chaindoc, başarısız webhook teslimatlarını üstel geri çekilme ile otomatik olarak yeniden dener.

  • 1. yeniden deneme: 1 dakika sonra
  • 2. yeniden deneme: 5 dakika sonra (toplam: 6 dakika)
  • 3. yeniden deneme: 15 dakika sonra (toplam: 21 dakika)

Webhook'ları test etme

Yerel geliştirme

Webhook testi için yerel sunucunuzu dışarıya açmak üzere ngrok gibi araçları kullanın:

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

Manuel test

Örnek bir payload ile webhook endpoint'inizi test edin:

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

En iyi uygulamalar

  • İşlemeden önce her zaman webhook imzalarını doğrulayın
  • Zaman aşımlarını önlemek için hızlı yanıt verin (30 saniye altında)
  • Webhook'ları kuyrukta asenkron olarak işleyin
  • Yinelenen olayları ele almak için idempotency uygulayın
  • Hata ayıklama ve denetim için tüm webhook olaylarını loglayın
  • Dashboard'da webhook teslimat hatalarını izleyin
  • Güvenlik için HTTPS endpoint'leri kullanın
  • Tüm olay türlerini düzgün şekilde ele alın (bilinmeyen olayları yoksayın)

Üretim örneği

İşte veritabanı entegrasyonlu üretime hazır bir webhook handler:

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',
      txHash: payload.txHash,
      verifiedAt: new Date(),
    },
  });
}

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

Sonraki adımlar