Anti Sandboxللمطوّرين

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 يوقف الإعادة فوراً. «تدوير السر» يوقّع بالسرّين ٢٤ ساعة ثم يسقط القديم.