# Webhooks

> أحداث موقعة، قابلة لإعادة المحاولة، ومختصرة لتحديثات الموارد، وتغييرات الموافقات، والمستندات النهائية، مع التحقق من التوقيع بلغات TypeScript و Dart و Python.

Source: https://developers.anpheros.com/ar/guides/webhooks

يمكن لمنصة Anpheros استدعاء خادمك عند حدوث شيء ما في مشروعك: تم كتابة مورد، منح شخص ما أو ألغى الوصول، تم الانتهاء من مستند. الأحداث **مختصرة**: معرفات، نوع، إصدار — لا تحتوي أبدًا على محتوى سريري. اقرأ المورد عبر الـ API إذا كنت بحاجة إليه. يتم توقيع كل تسليم؛ تحقق من التوقيع قبل الوثوق به.

## تسجيل نقطة نهاية

```bash
curl -X POST $BASE/v1/webhooks -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://your.app/anpheros/hook", "events": ["resource.created", "consent.revoked"], "description": "prod"}'
```

يحتوي الرد على `secret` (`whsec_…`) **مرة واحدة**. قم بتخزينها؛ ستحتاجها للتحقق من التوقيعات.
`events` يقبل `["*"]` أو أيًا مما يلي:

| type | when | `data` |
|---|---|---|
| `resource.created` / `resource.updated` / `resource.deleted` | مشروعك كتب مورد FHIR (REST v1، FHIR API أو حزمة) — **أو كتب مشروع آخر في سجل يمكنك الوصول إليه** (لقد أنشأت المريض، أو لديك منحة نشطة)؛ ثم `data.external` هو `true` و `data.source` يقول من كتبه: `author_type` (patient / practitioner / device / import / ai)، `organization` (اسم العرض الذي تصرف الكاتب نيابة عنه)، `practitioner` (الاسم من الدليل، عندما يكون معروفًا)، `source_system` | `resource_type`، `id`، `patient` (المعرف الذي تراه)، `version`، `last_updated`، وللكتابات الخارجية `external`، `source` |
| `consent.granted` | منح شخص ما تطبيقك حق الوصول من صفحة الموافقة (أو ربط تطبيق موثوق بسجله) | `grant_id`، `patient`، `application_id`، `scopes`، `purpose`، `expires_at` |
| `consent.revoked` | أنهى الشخص (أو المنصة) منحة | نفس ما سبق بالإضافة إلى `revoked_at`، `reason` |
| `document.finalized` | تم الانتهاء من تحميل مستند وتم التحقق من المجموع الاختباري | `id`، `patient`، `content_type`، `size`، `sha256` |
| `proposal.created` | اقترح وكيل ذكاء اصطناعي كتابة في سجل يمكنك رؤيته؛ ينتظر موافقة الشخص | `id`، `patient`، `action`، `resource_type` |
| `ping` | طلبت تسليم اختبار | `endpoint` |

نقاط النهاية لكل مشروع **وبيئته**: مفتاح `sk_test_` يدير نقاط نهاية sandbox،
مفتاح `sk_live_` يدير نقاط نهاية الإنتاج. يجب أن تكون عناوين URL نقاط نهاية `https://` عامة. التسجيل (وأي تغيير لاحق لـ `url`) يرفض العناوين المحلية والخاصة وعناوين link-local والبيانات الوصفية والعناوين المحجوزة الأخرى — `localhost`، `127.0.0.1`، `::1`، `10.0.0.0/8`، `192.168.0.0/16`، `fd00::/8` وما شابه — والأسماء التي تحلها إليها، مع `400` `invalid` في الحقل `url`؛ ينطبق هذا في sandbox أيضًا. يتحقق كل تسليم من العنوان مرة أخرى ويتصل فقط بعناوين IP التي تم التحقق منها للتو، لذلك لا يحصل اسم يشير لاحقًا إلى شبكة خاصة (إعادة ربط DNS) على أي شيء. لتلقي الأحداث على جهازك الخاص أثناء التطوير، قم بتعريضه عبر نفق HTTPS عام وسجل عنوان النفق.
ما يصل إلى 10 نقاط نهاية لكل مشروع وبيئة.

## التسليم

```
POST https://your.app/anpheros/hook
Content-Type: application/json
User-Agent: Anpheros-Webhooks/1.0
Anpheros-Event: resource.created
Anpheros-Event-Id: evt_…
Anpheros-Delivery: whd_…
Anpheros-Signature: t=1789800000,v1=5f3a…

{"id":"evt_…","type":"resource.created","created_at":"2026-09-19T12:00:00.000Z","environment":"production",
 "project":"proj_…","data":{"resource_type":"Observation","id":"…","patient":"…","version":1,"last_updated":"…"}}
```

استجب بأي `2xx` خلال 10 ثوانٍ. أي شيء آخر (أو انتهاء مهلة) يعتبر فشلاً، ويتم إعادة محاولة التسليم مع تراجع لمدة تصل إلى 24 ساعة (10 محاولات)، ثم يتم وضع علامة `exhausted`.
التسليمات هي "مرة واحدة على الأقل" وقد تصل بترتيب غير صحيح: استخدم `Anpheros-Event-Id`
لإلغاء التكرار و `data.version` لتجاهل التحديثات القديمة.

## التحقق من التوقيع

`v1` هو HMAC-SHA256 مع سرك الخاص فوق `<t>.<raw body>`؛ ارفض إذا كان `t` أقدم من 5 دقائق.

```ts
import { verifyWebhookSignature } from '@anpheros/sdk';
app.post('/anpheros/hook', express.raw({ type: 'application/json' }), async (req, res) => {
  const ok = await verifyWebhookSignature({ secret: process.env.ANPHEROS_WEBHOOK_SECRET!, header: req.header('anpheros-signature'), body: req.body });
  if (!ok) return res.status(400).end();
  const event = JSON.parse(req.body.toString());
  // … enqueue and return quickly
  res.status(204).end();
});
```

```dart
import 'package:anpheros_sdk/anpheros_sdk.dart';
final ok = verifyWebhookSignature(secret: secret, header: request.headers['anpheros-signature'], body: rawBodyBytes);
```

```python
import hmac, hashlib, time
def verify(secret: str, header: str, body: bytes, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
```

## التشغيل

- `GET /v1/webhooks/{id}/deliveries?status=failed` — السجل، الأحدث أولاً، مع رمز الحالة والخطأ.
- `POST /v1/webhooks/{id}/deliveries/{delivery_id}/redeliver` — إرسال واحد مرة أخرى الآن.
- `POST /v1/webhooks/{id}/ping` — حدث اختباري.
- `POST /v1/webhooks/{id}/rotate` — سر جديد (القديم يتوقف فورًا؛ قم بتحديث خادمك أولاً إذا لم تستطع تحمل وجود فجوة: سجل نقطة نهاية ثانية، ثم احذف القديمة).
- `PATCH /v1/webhooks/{id}` مع `{"active": false}` يوقف عمليات التسليم؛ يتم إسقاط المعلقات عند حذف نقطة النهاية.

كلا SDKs يعرض هذا تحت `webhooks` (`create`، `list`، `get`، `update`، `delete`، `rotateSecret`، `ping`، `deliveries`، `redeliver`).
