رفتن به محتوای اصلی
Certyneo
مستندات توسعه‌دهنده

Webhooks — دریافت رویدادهای امضا در زمان واقعی

یک آدرس URL HTTPS را در داشبورد Certyneo پیکربندی کنید و هر بار که رویدادی بر روی پاکت‌های شما رخ دهد (امضا، رد، انقضا) یک POST امضاشده HMAC-SHA256 دریافت کنید. 8 رویداد پشتیبانی‌شده، تکرار نمایی در 6 تلاش، تأیید رمزنگاری در 8 خط کد.

< 5s

میانگین تأخیر تحویل پس از رویداد

5x

تلاش‌ها در صورت شکست (تا حدود 9 ساعت بعد)

HMAC-SHA256

الگوریتم امضای هر درخواست

کاتالوگ رویدادها

8 رویداد زیر کل چرخه زندگی یک پاکت Certyneo را پوشش می‌دهند. آن‌هایی را که به شما علاقه‌مند هستید در تنظیمات → Webhooks فعال کنید، بقیه را نادیده بگیرید — اشتراک به‌صورت دانه‌ای بر اساس رویداد است.

رویدادراه‌اندازی
envelope.createdیک پاکت ایجاد می‌شود (از طریق UI، API یا الگو) — برای همزمان‌سازی یک رکورد CRM هنگام ایجاد مفید است.
envelope.sentپاکت به امضاکنندگان ارسال می‌شود (اولین ایمیل ارسال شد). آغاز چرخه امضای فعال را نشان می‌دهد.
envelope.completedتمام امضاکنندگان امضا کردند. PDF مهرشده eIDAS و ردیابی حسابرسی از طریق `proof_pdf_url` در بار دسترسی‌پذیر است.
envelope.declinedیک امضاکننده پاکت را رد کرد. دلیل رد (در صورت ارائه توسط امضاکننده) در `data.decline_reason` است.
envelope.voidedپاکت توسط فرستنده قبل از امضای کامل لغو شد. متفاوت از `expired` (انسانی در برابر timeout).
envelope.expiredتاریخ انقضای پاکت بدون امضای کامل سپری شد. فهرست امضاکنندگان گمشده در `data.missing_signers` است.
recipient.signedیک امضاکننده منفرد امضا کرد (اما لزوماً همه نه). برای گردش‌های کاری متوالی مفید است: راه‌اندازی انتقال به امضاکننده بعدی.
recipient.viewedیک امضاکننده پیوند امضا را باز کرد بدون امضا کردن هنوز. برای یادآوری‌های فروش هدفمند مفید است.

قالب Payload

تمام رویدادها از یک طرح JSON یکسان در سطح بالا پیروی می‌کنند: `event`, `envelope_id`, `occurred_at`, `data`. محتوای `data` بسته به رویداد متفاوت است (فیلدهای موجود برای هر رویداد در مرجع API توثیق شده‌اند). در اینجا یک نمونه `envelope.completed` کامل آمده است.

{
  "event": "envelope.completed",
  "envelope_id": "env_01HG3K8X9Y2N4P5Q6R7S8T9V0W",
  "occurred_at": "2026-05-27T08:42:13.521Z",
  "data": {
    "title": "Contrat de prestation Acme Corp",
    "status": "completed",
    "created_at": "2026-05-24T14:12:00.000Z",
    "completed_at": "2026-05-27T08:42:13.000Z",
    "signers": [
      {
        "email": "client@acme.example",
        "name": "Alex Client",
        "signed_at": "2026-05-27T08:42:13.000Z",
        "method": "eidas_aes",
        "ip": "203.0.113.42"
      }
    ],
    "proof_pdf_url": "https://certyneo.com/api/envelopes/env_01HG.../proof.pdf"
  }
}

Payload با کدگذاری UTF-8 بدون BOM است. امضای HMAC بر روی بدنه خام همانطور که ارسال شده محاسبه می‌شود — فضاها را تغییر ندهید، تجزیه مجدد JSON اغلب ترتیب کلیدها را تغییر می‌دهد و تایید را شکست می‌دهد.

تایید امضای HMAC

هر درخواست با secret webhook شما امضا شده است (تنها یک بار هنگام ایجاد در داشبورد قابل مشاهده و سپس رمزگذاری شده در حالت سکون). امضا در هدر `Certyneo-Signature` به صورت `t=<timestamp>,v1=<hex_hmac>` منتقل می‌شود. همیشه امضا را قبل از پردازش payload تایید کنید — بدون این مرحله، هر کسی می‌تواند یک رویداد جعلی ایجاد کند و endpoint شما را فراخوانی کند.

Node.js / TypeScript

import crypto from "node:crypto";

export function verifyCertyneoSignature(
  rawBody: string,
  signatureHeader: string,
  secret: string,
): boolean {
  // Header format: t=<timestamp>,v1=<hex_hmac>
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("=")),
  );
  const ts = parts.t;
  const sig = parts.v1;
  if (!ts || !sig) return false;

  // Reject events older than 5 minutes (replay protection).
  const age = Math.abs(Date.now() / 1000 - Number(ts));
  if (age > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");

  // timingSafeEqual to mitigate timing attacks.
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(sig, "hex"),
  );
}

Python

import hashlib
import hmac
import time


def verify_certyneo_signature(
    raw_body: bytes, signature_header: str, secret: str
) -> bool:
    """Verify a Certyneo webhook signature.

    Header format: `t=<timestamp>,v1=<hex_hmac>`
    Rejects events older than 5 minutes (replay protection).
    """
    parts = dict(p.split("=") for p in signature_header.split(","))
    ts = parts.get("t")
    sig = parts.get("v1")
    if not ts or not sig:
        return False

    if abs(time.time() - int(ts)) > 300:
        return False

    expected = hmac.new(
        secret.encode("utf-8"),
        f"{ts}.{raw_body.decode('utf-8')}".encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(expected, sig)

خطای متداول

از `===` یا `==` برای مقایسه امضای مورد انتظار با امضای دریافتی استفاده نکنید. از یک تابع timing-safe استفاده کنید (`crypto.timingSafeEqual` در Node، `hmac.compare_digest` در Python). بدون این، تفاوت زمان مقایسه بین دو امضا به تدریج secret را به یک مهاجم صبور نشان می‌دهد (timing attack).

سیاست تلاش مجدد

اگر endpoint شما چیزی غیر از HTTP 2xx پاسخ دهد (timeout، 5xx، connection refused)، ما بر اساس backoff exponential تلاش مجدد می‌کنیم. در مجموع 6 تلاش در حدود 9 ساعت. پس از 6 تلاش، رویداد به عنوان `failed` در داشبورد webhook شما علامت گذاری می‌شود و ایمیل هشدار ارسال می‌شود.

تلاشتاخیر تجمعیزمان سپری شده
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 36

اگر می‌خواهید یک رویداد ناموفق را به صورت دستی ارسال مجدد کنید، داشبورد webhook یک دکمه «Re-deliver» برای هر تحویل ناموفق ارائه می‌دهد — در طول 7 روز پس از شکست نهایی در دسترس است.

تست بدون ارسال پاکت واقعی

endpoint `/v1/webhooks/test` یک URL و نوع رویداد را می‌پذیرد و یک payload فرضی را دقیقاً مانند یک مورد واقعی POST می‌کند. ایده‌آل برای تایید handler شما در ادغام مداوم یا در طول توسعه محلی (با ngrok یا معادل).

# Trigger a test webhook to your endpoint
curl -X POST https://api.certyneo.com/v1/webhooks/test \
  -H "Authorization: Bearer $CERTYNEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event": "envelope.completed", "url": "https://your.app/webhooks/certyneo"}'

6 شیوه‌ای برای رعایت

  • امضای HMAC را قبل از هر خواندن از body تایید کنید — از مقایسه timing-safe استفاده کنید.
  • هر `envelope_id` × `event` را تنها یک بار پردازش کنید با ذخیره‌سازی ID‌های دیده شده در پایگاه داده (تلاش‌های مجدد می‌توانند رویداد یکسان را دوباره تحویل دهند).
  • درون 5 ثانیه حداکثر HTTP 200 پاسخ دهید، سپس به صورت ناهمزمان پردازش کنید (queue). در غیر این صورت timeout خواهید شد و به عنوان retry شمارش خواهید شد.
  • در debug بدنه خام + امضای کامل را log کنید — تایید HMAC اغلب بر روی BOM یا whitespace نامرئی شکست می‌خورد.
  • یک صف dead-letter queue را به رویدادهای `failed` متصل کنید تا در صورت بروز حادثه در سمت سرویس خود، به صورت دستی تحویل مجدد انجام دهید.
  • تفاوت 5 دقیقه‌ای بین `t=` و زمان دریافت را تحمل کنید — بیشتر از این، رد کنید تا replays را مسدود کنید.

برای اطلاعات بیشتر

آماده‌اید سیستم‌های خود را متصل کنید؟

یک حساب رایگان در 2 دقیقه ایجاد کنید — پیکربندی webhook از طرح Starter (رایگان، 5 پاکت/ماه) در دسترس است.