واجهة REST API (Tool API وخادم إلى خادم)
حزمة المتصفح تتخاطب مع Tool API. يمكنك استدعاء الواجهة نفسها من خادمك الخلفي — لتسجيل الموافقات التي تُجمع في واجهتك الخاصة، أو لإصدار رموز مميزة (tokens) للموافقة لمرة واحدة، أو للبحث عن السجلات والتحقق منها.
عنوان URL الأساسي: https://system.hashentry.com
| الطريقة | المسار | الغرض | المصادقة |
|---|---|---|---|
GET | /tool/v1/config | تكوين اللافتة، المستندات المرفقة، المنطقة المكتشَفة | مفتاح API (قراءة) |
GET | /tool/v1/documents/{slug}/active | الإصدار النشط من مستند واحد، مع المحتوى | مفتاح API (قراءة) |
GET | /tool/v1/documents/active | المستندات النشطة المرفقة بالموقع | مفتاح API (قراءة) |
POST | /tool/v1/consents | تسجيل موافقة واحدة (مسار حزمة المتصفح / الجوال) | مفتاح API (كتابة) + توقيع |
POST | /tool/v1/consents/batch | تسجيل ما يصل إلى 100 موافقة | مفتاح API (كتابة) + توقيع |
POST | /tool/v1/s2s/consents | تسجيل موافقة واحدة من خادمك الخلفي | مفتاح API (كتابة) + JWT + توقيع |
POST | /tool/v1/s2s/consents/batch | تسجيل ما يصل إلى 100 موافقة من خادمك الخلفي | مفتاح API (كتابة) + JWT + توقيع |
POST | /tool/v1/s2s/approval-tokens | إصدار رمز مميز لمرة واحدة للموافقة على مستند | مفتاح API (كتابة) + JWT + توقيع |
GET | /tool/v1/consents/status | الموافقات الحالية لزائر واحد | مفتاح API (قراءة) |
GET | /tool/v1/consents/verify/{id} | فحص سلامة سجل واحد | مفتاح API (قراءة) |
GET POST | /api/v1/public/consents/verify | فحص سلامة عام (بلا مفتاح) | لا شيء |
المصادقة
Section titled “المصادقة”مفتاح API
Section titled “مفتاح API”كل طلب إلى Tool API يحمل مفتاح الموقع:
X-API-Key: he_live_…للمفتاح نطاق صلاحية: read يسمح بنقاط النهاية GET، و write يسمح إضافةً إلى ذلك بـ POST. يجب أن يكون الموقع موثَّقًا في لوحة التحكم. إذا كانت للموقع قائمة أصول (origins) مسموح بها، تُرفض طلبات المتصفح الآتية من أصول أخرى؛ وإذا كانت له قائمة عناوين IP مسموح بها، تُرفض الطلبات الآتية من عناوين أخرى.
توقيع الطلب (جميع طلبات POST)
Section titled “توقيع الطلب (جميع طلبات POST)”X-Signature: <HMAC-SHA256 بصيغة hex>X-Timestamp: <وقت Unix بالثواني>X-Nonce: <قيمة فريدة لكل طلب>body_hash = SHA-256(جسم الطلب الخام) (hex بأحرف صغيرة)canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + body_hashsignature = HMAC-SHA256(key = مفتاح API, message = canonical) (hex بأحرف صغيرة)METHODبأحرف كبيرة.PATHهو مسار الطلب متضمنًا/tool/v1، دون سلسلة الاستعلام، مثل/tool/v1/s2s/consents.- تُحسب تجزئة الجسم على البايتات نفسها التي ترسلها بالضبط. حوّل الـ JSON إلى سلسلة مرة واحدة وأرسل تلك السلسلة نفسها.
- يجب أن يكون
X-Timestampضمن ±300 ثانية من وقت الخادم. X-Nonceيتكون من 8–128 محرفًا منA–Z a–z 0–9 . _ : -(يصلح UUID) ولا يمكن استخدامه إلا مرة واحدة لكل موقع؛ ويُرفض التكرار بـNONCE_REPLAYED.
مثال محلول (متجه الاختبار المشترك بين الحزمة والـ API):
API key : he_live_0123456789abcdef0123456789abcdefMethod : POSTPath : /tool/v1/consentsTimestamp : 1760000000Nonce : 3f2b8c1e-5a47-4d0e-9b6a-7c1d2e3f4a5bBody : {"consent_type":"cookie","decision":"accepted","language":"tr","idempotency_key":"9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d","metadata":{"note":"çerez ✓"}}
body_hash : 93316d8d6adb505a19668f4022e4e33b12aed9c0c4bb97d205b5064257090d34canonical : POST\n/tool/v1/consents\n1760000000\n3f2b8c1e-5a47-4d0e-9b6a-7c1d2e3f4a5b\n93316d8d6adb505a19668f4022e4e33b12aed9c0c4bb97d205b5064257090d34signature : e108683927badf35c36d5f467f63a9704c131d0102f14b06af7ed3046d44a8be(\n يمثل محرف تغذية سطر واحدًا؛ والجسم بترميز UTF-8.) إذا أنتج تنفيذك هذا التوقيع لهذه المدخلات فهو صحيح.
const crypto = require('crypto');
function sign(apiKey, method, path, body) { const timestamp = Math.floor(Date.now() / 1000).toString(); const nonce = crypto.randomUUID(); const bodyHash = crypto.createHash('sha256').update(body, 'utf8').digest('hex'); const canonical = [method.toUpperCase(), path, timestamp, nonce, bodyHash].join('\n'); const signature = crypto.createHmac('sha256', apiKey).update(canonical, 'utf8').digest('hex'); return { 'X-Signature': signature, 'X-Timestamp': timestamp, 'X-Nonce': nonce };}
const body = JSON.stringify({ consent_type: 'document_approval', decision: 'accepted', /* … */ });const headers = { 'Content-Type': 'application/json', 'X-API-Key': apiKey, Authorization: `Bearer ${jwt}`, // مسارات S2S فقط ...sign(apiKey, 'POST', '/tool/v1/s2s/consents', body),};التوقيع يثبت أن الطلب أُجري بمفتاح API وأنه لم يُعَد تشغيله. ولأن حزمة المتصفح تستخدم المفتاح نفسه، وهو ظاهر في مصدر الصفحة، فالتوقيع وحده لا يثبت أن الطلب جاء من خادمك الخلفي — وهذا ما وُجدت مسارات S2S من أجله.
مسارات خادم إلى خادم (/tool/v1/s2s/…)
Section titled “مسارات خادم إلى خادم (/tool/v1/s2s/…)”تحتاج طلبات S2S إلى ثلاثة أشياء: مفتاح API بصلاحية كتابة، وتوقيع الطلب، و JWT لمستخدم له الدور api_user:
- ادعُ عضوًا في الفريق بالدور
api_userمن لوحة التحكم. هذا الدور يستطيع استخدام S2S API فقط؛ ولا يستطيع تسجيل الدخول إلى اللوحة. - سجّل الدخول عبر
POST /api/v1/auth/loginللحصول على JWT؛ وجدّده عبرPOST /api/v1/auth/refresh. - أرسل
Authorization: Bearer <jwt>معX-API-Keyوترويسات التوقيع. يجب أن ينتمي المستخدم إلى المستأجر (tenant) الذي يملك الموقع.
طلبات S2S وحدها يجوز لها ضبط ip_address و user_agent و otp_verified و otp_reference. على مسار المتصفح يُؤخذ عنوان IP و user agent دائمًا من الاتصال، وتُتجاهل حقول OTP.
تسجيل موافقة
Section titled “تسجيل موافقة”POST /tool/v1/consents (أو /tool/v1/s2s/consents)
{ "consent_type": "cookie", "decision": "partial", "document_version_id": "0190a1b2-…", "language": "en", "categories": { "necessary": true, "analytics": true, "marketing": false }, "idempotency_key": "9a1c2b3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d", "visitor_id": "user-4711", "visitor_region": "DE", "compliance_framework": "gdpr", "metadata": { "order_id": "ORD-54321" }}| الحقل | القواعد |
|---|---|
consent_type | مطلوب. حتى 50 محرفًا (A–Z a–z 0–9 _ - .). المستخدَم حاليًا: cookie و document_approval و do_not_sell_opt_out. |
decision | مطلوب. accepted أو rejected أو partial أو withdrawn. |
document_version_id | UUID لإصدار نشط من مستند مرفق بالموقع. بدونه — أو مع إصدار غير نشط — يُرفض الطلب بـ 409 CONFIG_OUTDATED. الموافقة على ملفات تعريف الارتباط تشير إلى مستند cookie_policy الخاص بالموقع. |
language | إن أُعطيت، يجب أن تكون لغة ذلك الإصدار. |
categories | كائن من مفتاح الفئة ← قيمة منطقية. لا تُخزَّن إلا الفئات المعرَّفة في تكوين اللافتة. |
idempotency_key | 8–64 محرفًا (A–Z a–z 0–9 . _ : -). انظر أدناه. |
visitor_id | مرجعك الخاص للشخص، حتى 100 محرف. |
visitor_region, visitor_country | حتى 10 / 2 محارف. |
compliance_framework | kvkk أو gdpr أو uk_gdpr أو ccpa أو lgpd أو popia أو pipeda أو pipl أو pipa أو none. |
source | حتى 20 محرفًا، مثل client و server. |
metadata, extra_fields | كائنات بـ 30 مفتاحًا على الأكثر ونحو 16 KB لكل منها. |
approval_token | رمز مميز لمرة واحدة لموافقة من نوع document_approval (انظر أدناه). |
| حقول التوقيع / المستند المخصص | signature_image, signature_mime_type, signer_name, signer_email, signer_phone, signer_id_number, declaration_text, drawing_duration_ms, stroke_count, strokes_data, input_type, screen_resolution, custom_document_content, custom_document_mime_type — كلها في المستوى الأعلى. راجع الموافقة على المستندات والتوقيع. |
ip_address, user_agent, otp_verified, otp_reference | S2S فقط. |
احصل على document_version_id من GET /tool/v1/config?language=en (data.documents[].version.id) أو من GET /tool/v1/documents/{slug}/active?language=en.
الرد: 202 Accepted
Section titled “الرد: 202 Accepted”{ "message": "…", "data": { "consent_token": "ct_…", "proof_hash": "…64 hex characters…", "created_at": "2026-10-10T10:00:00+00:00", "duplicate": false }}202 يعني أن الطلب اجتاز جميع الفحوص وأن السجل قُبل للكتابة. الرمز المميز وتجزئة الإثبات (proof hash) والتوقيت نهائية عند تلك النقطة، لكن الصف يكتبه عامل في الخلفية، ولذلك قد لا يجده status و verify للحظة قصيرة.
idempotency_key
Section titled “idempotency_key”أرسل مفتاحًا واحدًا لكل إجراء من المستخدم وأعد استخدامه في كل إعادة محاولة لذلك الإجراء. الطلب المكرر بالمفتاح نفسه لا ينشئ سجلًا ثانيًا ولا يستهلك من الحصة؛ بل يعيد الرد الأصلي مع "duplicate": true. تُحفظ المفاتيح مدة 24 ساعة. حزمة المتصفح تولّد UUID واحدًا لكل قرار وتستخدمه في المحاولة الأولى، وفي إعادات المحاولة من جهة العميل، وفي إعادات الإرسال من قائمة انتظارها غير المتصلة.
الدفعات (Batch)
Section titled “الدفعات (Batch)”POST /tool/v1/consents/batch مع { "consents": [ { … }, … ] } (من 1 إلى 100 عنصر، بالحقول نفسها المذكورة أعلاه).
{ "data": { "accepted": 2, "rejected": 1, "results": [ { "index": 0, "status": "accepted", "consent_token": "ct_…", "proof_hash": "…", "created_at": "…", "duplicate": false }, { "index": 1, "status": "accepted", "consent_token": "ct_…", "proof_hash": "…", "created_at": "…", "duplicate": true }, { "index": 2, "status": "rejected", "error": "…", "error_code": "CONSENT_LIMIT_EXCEEDED", "code": 429 } ] }}تكون الحالة 202 عندما تُعالَج الدفعة؛ افحص كل مُدخل في results. أخطاء التحقق من الصحة، ووجود عنصر بإصدار مستند غير معروف أو غير نشط، ترفض الدفعة كلها (422 / 409).
الرموز المميزة للموافقة لمرة واحدة
Section titled “الرموز المميزة للموافقة لمرة واحدة”POST /tool/v1/s2s/approval-tokens — S2S فقط.
{ "document_slug": "service-contract", "subject": "user-4711", "signer_name": "Jane Doe", "expires_in": 600}| الحقل | القواعد |
|---|---|
document_slug / document_version_id | واحد منهما على الأقل. مع معرّف إصدار يكون الرمز صالحًا لذلك الإصدار فقط. |
subject | مطلوب. مرجعك الخاص للشخص (100 محرف كحد أقصى)؛ يُخزَّن بصفته visitor_id للسجل. |
signer_name, signer_email, signer_phone, signer_id_number | اختياري. بيانات الهوية التي تشهد بها. |
otp_verified, otp_reference | اختياري. صرّح بفحص OTP أجريته أنت. |
custom_document_hash | اختياري. SHA-256 (hex) لمستند أنشأه نظامك؛ ويجب عندئذ أن تحمل الموافقة ذلك المحتوى بالضبط. |
expires_in | بالثواني، 30–3600. الافتراضي 600. |
الرد 201:
{ "data": { "approval_token": "hat_…", "approval_token_id": "atk_…", "expires_in": 600, "expires_at": "2026-10-10T10:10:00+00:00", "document_id": "…", "document_slug": "service-contract", "document_version_id": null }}أعطِ approval_token للصفحة ومرّره إلى الحزمة (approvalToken) أو أرسله في الحقل approval_token في المستوى الأعلى لموافقة من نوع document_approval. يستهلكه الخادم مرة واحدة، ويأخذ الهوية من الرمز، ويسجّل identity_assurance: tenant_attested مع القيمة غير السرية approval_token_id. الطلب الذي يُرفض لسبب آخر (حد المعدل، الحصة) لا يستهلك الرمز. لا يخزّن HashEntry إلا تجزئة للرمز.
القراءة والتحقق
Section titled “القراءة والتحقق”GET /tool/v1/consents/status?visitor_id=…
Section titled “GET /tool/v1/consents/status?visitor_id=…”يعيد ما يصل إلى 20 موافقة غير منتهية الصلاحية لقيمة visitor_id واحدة، الأحدث أولًا. عوامل تصفية اختيارية: consent_type و document_slug و document_version_id.
GET /tool/v1/consents/verify/{id}
Section titled “GET /tool/v1/consents/verify/{id}”{id} هو UUID لموافقة، أو consent_token (ct_…)، أو proof_hash.
{ "valid": true, "data": { "consent_type": "cookie", "decision": "accepted", "proof_hash": "…", "hash_version": 2, "hash_valid": true, "merkle_valid": null, "is_anonymized": false, "is_tsa_stamped": false, "created_at": "2026-10-10T10:00:00+00:00" }}ما يعنيه هذا الجواب — وما لا يعنيه:
valid: trueيعني: السجل يخص مستأجرك، وقد أعاد HashEntry حساب تجزئة الإثبات من السجل المخزَّن بالمفتاح السري لمستأجرك فطابقت، و— إذا كان السجل جزءًا من دفعة Merkle — إثبات تضمينه صحيح. وجود صف لا يكفي.merkle_validيكونnullعندما لا يكون للسجل إثبات Merkle.is_tsa_stampedيفيد بوجود رمز ختم زمني (timestamp token) للسجل (مباشرةً أو عبر دفعة Merkle الخاصة به). ولا يفيد بأن توقيع الرمز جرى التحقق منه في هذا الاستدعاء.- هذا فحص يجريه HashEntry على بيانات HashEntry. للتحقق دون الوثوق بجواب الخادم، صدّر حزمة أدلة وتحقق منها بنفسك: التحقق من الأدلة دون اتصال.
- المعرّف غير المعروف يعيد
404مع"valid": falseوcode: "CONSENT_NOT_FOUND".
GET|POST /api/v1/public/consents/verify
Section titled “GET|POST /api/v1/public/consents/verify”بلا مفتاح API؛ ومقيَّد بحد للمعدل. المعاملات: token (رمز ct_… أو قيمة ملف تعريف الارتباط _he_consent الخاص بالحزمة) أو proof_hash أو id. يعيد القرار والفئات ونطاق الموقع والتوقيتات وحالة التجزئة ومعلومات الختم الزمني، ولا يعيد أبدًا عنوان IP ولا user agent ولا معرّف الزائر.
- سجل موجود:
200معvalidكما هو موصوف أعلاه. - قيمة ملف تعريف ارتباط من المتصفح تتعذر مطابقتها مع سجل في الخادم:
200مع"valid": falseو"source": "client_token"و"server_record_found": falseوحقول ملف تعريف الارتباط بعد فك ترميزها. ملف تعريف الارتباط غير موقَّع، ولذلك لا يُبلَّغ عنه أبدًا بأنه صالح بمفرده. - لم يُعثر على شيء:
404مع"valid": false. بلا معرّف:422.
رموز الأخطاء
Section titled “رموز الأخطاء”ردود الأخطاء بصيغة JSON وتحمل رمزًا آليًا ثابتًا في code (وكذلك في error_code و error.code؛ وبعض أخطاء المصادقة تستخدم error.code فقط).
| HTTP | الرمز | المعنى |
|---|---|---|
| 401 | MISSING_API_KEY, INVALID_API_KEY | مفتاح API غائب أو غير معروف. |
| 401 | MISSING_SIGNATURE_HEADERS, TIMESTAMP_EXPIRED, INVALID_SIGNATURE, INVALID_NONCE, NONCE_REPLAYED | مشكلات في توقيع الطلب. |
| 401 | MISSING_BEARER_TOKEN, INVALID_BEARER_TOKEN | S2S: الـ JWT مفقود أو غير صالح. |
| 403 | SITE_NOT_VERIFIED | وثّق الموقع في لوحة التحكم. |
| 403 | ORIGIN_NOT_ALLOWED, IP_NOT_ALLOWED | ليس ضمن قائمة المسموح بهم للموقع. |
| 403 | INSUFFICIENT_ROLE, TENANT_MISMATCH | S2S: المستخدم ليس api_user لهذا المستأجر. |
| 403 | — | نطاق صلاحية مفتاح API لا يسمح بهذه الطريقة. |
| 404 | DOCUMENT_NOT_FOUND, VERSION_NOT_FOUND, CONSENT_NOT_FOUND | |
| 409 | CONFIG_OUTDATED | document_version_id مفقود، أو غير نشط، أو بلغة خاطئة، أو أن ذاكرة التكوين المؤقتة في الخادم باردة. أعد قراءة /tool/v1/config؛ ولا تُعد الإرسال إلا إذا كان الإصدار ما زال هو النشط. |
| 422 | VALIDATION_ERROR | أخطاء الحقول موجودة في errors. |
| 422 | APPROVAL_TOKEN_REQUIRED, APPROVAL_TOKEN_INVALID, APPROVAL_TOKEN_CONFLICT | راجع الرموز المميزة للموافقة لمرة واحدة. |
| 422 | INVALID_CATEGORIES, METADATA_TOO_MANY_KEYS, METADATA_TOO_LARGE, EXTRA_FIELDS_TOO_MANY_KEYS, EXTRA_FIELD_NAME_TOO_LONG, EXTRA_FIELDS_TOO_LARGE, STROKES_DATA_TOO_LARGE, INVALID_SIGNATURE_IMAGE, SIGNATURE_IMAGE_TOO_LARGE, INVALID_CUSTOM_DOCUMENT, UNSUPPORTED_CUSTOM_DOCUMENT_TYPE, CUSTOM_DOCUMENT_TOO_LARGE | حدود الحمولة. |
| 429 | RATE_LIMIT_EXCEEDED | حد المعدل الخاص بالخطة. انظر Retry-After و X-RateLimit-Limit و X-RateLimit-Remaining. |
| 429 | CONSENT_LIMIT_EXCEEDED, NO_CONSENT_SUBSCRIPTION | حصة الموافقات في الخطة استُنفدت / لا توجد خطة نشطة. |
| 429 | TOO_MANY_INVALID_API_KEYS | طلبات كثيرة جدًا بمفاتيح غير صالحة من عنوان IP واحد. |
| 503 | QUEUE_OVERLOADED, ADMISSION_UNAVAILABLE, API_KEY_LOOKUP_UNAVAILABLE | مؤقت. أعد المحاولة بعد Retry-After بالقيمة نفسها لـ idempotency_key. |
أعد محاولة الردود 408 و 425 و 429 و 5xx بالجسم نفسه وبالقيمة نفسها لـ idempotency_key (وبتوقيت و nonce وتوقيع جديدة). لا تُعد محاولة ردود 4xx الأخرى دون تغيير.