الانتقال إلى المحتوى الرئيسي
Certyneo
مستندات المطور

ويب هوكس تتلقى حوادث التوقيع في الوقت الحقيقي

حدد عنوان 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` في لوحة المراقبة الخاصة بك ويتم ارسال رسالة تنبيه.

محاولةالمدة المتراكمةوقت مضى
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 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 مظروفات/شهر).