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 شما علامت گذاری میشود و ایمیل هشدار ارسال میشود.
| تلاش | تاخیر تجمعی | زمان سپری شده |
|---|---|---|
| #1 | 0 | 0 |
| #2 | + 1 min | 1 min |
| #3 | + 5 min | 6 min |
| #4 | + 30 min | 36 min |
| #5 | + 2 h | 2h 36 |
| #6 | + 6 h | 8h 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 پاکت/ماه) در دسترس است.