ويب هوكس تتلقى حوادث التوقيع في الوقت الحقيقي
حدد عنوان URL HTTPS في لوحة التحكم Certyneo بتاعتك و تتلقى POST موقعة HMAC-SHA256 لما يجي حدث على المظروفات بتاعك: توقيع، رفض، انتهاء. 8 أحداث مدعومة، إعادة تجربة أوضاعية على 6 محاولات، التحقق التشفير في 8 خطوط من الرمز.
< 5s
متوسط وقت التسليم بعد الحدث
5x
محاولات في حالة فشل (حتى 9 ساعات بعد)
HMAC-SHA256
خوارزمية توقيع كل طلب
كتالوج الأحداث
الاحداث الـ8 اللي تحتها بتغطي دورة حياة كلي لفكرة Certyneo. شغلي اللي بتعجبك في الإعدادات → Webhooks، تجاهل الباقي الاشتراك بتكون حجرية لكل حدث.
| الحدث | تَحَرُّك |
|---|---|
envelope.created | كيسة تخلق (بوبجهة المستخدم، واجهة برمجيات التطبيق أو قالب) مفيدة لمزامنة تسجيل جانب CRM من وقت الإنشاء. |
envelope.sent | بيعدي المظروف للمتوقعين (اول ايميل تم إرساله). بيمثل بداية دورة التوقيع النشطة. |
envelope.completed | كل الموقعين وقعوا. الـ PDF المختوم eIDAS والمسار التدقيق موجود عن طريق `proof_pdf_url` في الحمولة. |
envelope.declined | واحد من الموقعين رفض المظروف. سبب رفض (إذا قدم من الموقع) موجود في `data.decline_reason`. |
envelope.voided | المظروف اتلغى من المصدر قبل الا يوقع كامل. متفرق من `expired` (انسان vs timeout). |
envelope.expired | تاريخ انتهاء المبلغ في المظروف تمت من غير توقيع كامل. قائمة الموقعين المفقودين في `data.missing_signers`. |
recipient.signed | واحد من الموقعين وقع (ولكن مش كلهم بالضرورة). مفيد لسلسلة عمل متتالية: يبدأ التحول إلى الموقع التالي. |
recipient.viewed | واحد من الموقعين فتح رابط التوقيع من غير توقع بعد، مفيد لإعادة التجارة المستهدفة. |
شكل الحمولة
كل الحوادث بتشارك نفس المخطط 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"
}
}الحملة المفيدة مشفرة UTF-8 ، بدون BOM. يتم حساب توقيع HMAC على الجسم الخام كما تم إرساله لا تغير المساحات ، غالبًا ما يغير إعادة تحليل JSON ترتيب المفاتيح ويخرب التحقق.
تحقق من توقيع HMAC
كل طلب بيتم توقيعه بسر webhook بتاعك (بيتم رؤيته مرة واحدة بس لما تخلق في لوحة التحكم، وبعدها بيتم تشفيره عند الراحة) ويتم نقل التوقيع في عنوان `Certyneo-Signature` بتنسيق `t=<timestamp>,v1=<hex_hmac>`. دائماً تحقق من التوقيع قبل معالجة الحمولة من غير هذه الخطوة، أي واحد ممكن يخلق حدث ويدعو نقطة النهاية.
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). و إلا فان فاصل وقت المقارنة بين التوقيعين يظهر السر تدريجيا لمهاجم مريض (timing attack).
سياسة إعادة التجربة
لو الرد النهائي بتاعك غير HTTP 2xx (timeout, 5xx, connection refused) ، فانا بنترد على حساب الاحتياط التراكمي. مجموع 6 محاولات خلال 9 ساعات. بعد 6، بيكون الحدث علامة `failed` في لوحة المراقبة الخاصة بك ويتم ارسال رسالة تنبيه.
| محاولة | المدة المتراكمة | وقت مضى |
|---|---|---|
| #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 |
لو عايز تبعث حدث فاشل يدوياً، فإن لوحة التحكم في الويب هي بطانة إعادة تسليم لكل تسليم فاشل متاحة لمدة 7 أيام بعد الفشل النهائي.
اختبار بدون ارسال ظرف حقيقي
نقطة النهاية `/v1/webhooks/test` بتقبل عنوان URL ونوع حدث، وتبيع حمولة وهمية موقعة تمامًا كحمولة حقيقية. مثالية للتحقق من صحة المتحكم الخاص بك في التكامل المستمر أو أثناء التطوير المحلي (مع 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 قبل أي قراءة للجسم استخدم مقارنة آمنة بالوقت.
- معالجة كل `envelope_id` × `event` مرة واحدة بس بتخزن المميزات اللي شفتها من قبل في القاعدة (التسجيلات ممكن تعيد نفس الحدث).
- رد على HTTP 200 في 5 ثواني أكتر، وبعدين عمل بشكل غير متزامن (الانسحاب). لو كده، فستكون في وقت توقف وبتعد كمحاولة تانية.
- تسجيل الجسم الخام + التوقيع الكامل في التحليل الاختباري التحقق من HMAC غالبا ما يفشل على BOM أو مساحة بيضاء غير مرئية.
- وصل خط موت متأخر على الحدث `فشل ` علشان يمرر يدويا في حالة حدوث حادث على جانب قسمك.
- يسمح بفترة 5 دقايق بين `t=` و وقت الاستقبال بعد كده، ويرفض علشان يحجب التكرار.
علشان نروح اكتر
جاهز علشان نصل نظامك؟
انشاء حساب مجاني في دقايق 2 تكوين webhook موجود من الخطة البداية (مجاني، 5 مظروفات/شهر).