Webhooks
نرسل الأحداث بـ POST إلى نقطة HTTPS تسجّلها. كل إرسال موقّع؛ تحقق من التوقيع، ارفض الإعادة، أجب 2xx بسرعة، ونفّذ العمل بعدها.
التسجيل
curl -s -X POST "https://api.antisandbox.com/v1/webhook-endpoints" -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{ "url": "https://erp.example.com/antisandbox/hook", "description": "ERP", "subscribed_events": ["*"] }'الرد يحمل signing_secret (whsec_…) مرة واحدة. نخزّنه مشفّراً ولا يظهر ثانية. الشيء نفسه من شاشة التطبيق ← Webhooks.
قواعد الرابط، تُفحص عند الحفظ وتُعاد قبل كل إرسال: https في الإنتاج (السندبوكس يقبل http)، لا بيانات اعتماد ولا fragment، منفذ 80/443 أو 1024–65535، ولا عنوان خاص أو محلي أو link-local أو metadata، ولا localhost/*.local/*.internal.
أنواع الأحداث
| type | متى | data |
|---|---|---|
webhook.test | ضغطت «اختبار» أو POST …/test | { endpoint_id, message, sent_at } |
inbound_event.accepted | حُفظ حدث وارد جديد | { event_id, external_message_id, channel, sandbox } |
الإرسال
POST /antisandbox/hook
content-type: application/json; charset=utf-8
X-AntiSandbox-Event: inbound_event.accepted
X-AntiSandbox-Delivery: 0b7c…
X-AntiSandbox-Timestamp: 1725436800
X-AntiSandbox-Signature: t=1725436800,v1=8a1f…[,v1=…]
{ "id": "<event id>", "type": "inbound_event.accepted", "version": "2026-09-01", "environment": "sandbox",
"occurred_at": "2026-09-04T08:15:00.000Z", "subject": { "type": "inbound_event", "id": "…" }, "data": { … } }التحقق من التوقيع
v1 = hex HMAC-SHA256(secret, "<t>.<delivery id>.<raw body>") فوق البايتات كما وصلت، مقارنة ثابتة الزمن، ورفض t أبعد من خمس دقائق. أثناء التدوير توجد قيمتا v1 — اقبل إذا طابقت أي منهما أي سر تملكه. ثم امنع التكرار بمعرّف الإرسال.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(header, body, deliveryId, secrets, now = Math.floor(Date.now() / 1000)) {
const parts = header.split(',').map((p) => p.trim());
const t = Number(parts.find((p) => p.startsWith('t='))?.slice(2));
const sigs = parts.filter((p) => p.startsWith('v1=')).map((p) => Buffer.from(p.slice(3), 'hex'));
if (!Number.isFinite(t) || Math.abs(now - t) > 300) return false;
return secrets.some((s) => {
const expected = createHmac('sha256', s).update(`${t}.${deliveryId}.${body}`).digest();
return sigs.some((sig) => sig.length === expected.length && timingSafeEqual(sig, expected));
});
}إعادة المحاولة والتدوير
أجب 2xx خلال ١٠ ثوانٍ. غير ذلك يُعاد: ١ د ← ٥ د ← ٣٠ د ← ٢ س ← ٦ س، خمس محاولات ثم «dead» وتظهر في المكتب وفي تنبيهاتنا. 410 يوقف الإعادة فوراً. «تدوير السر» يوقّع بالسرّين ٢٤ ساعة ثم يسقط القديم.
