Webhooks وIPN / APN
تُخطر SOAMTAR خادمك فوريًا عبر webhooks، المعروفة أيضًا بـ IPN (إشعار الدفع الفوري) أو APN (إشعار الدفع الآلي).
- IPN: إشعار فوري بمجرد تأكيد الدفع (مثل
payment.success). - APN: إشعارات آلية لتغيّرات الحالة الثانوية وأحداث دورة الحياة (مثل
payment.expired).
عمليات الإرسال موقَّعة (HMAC-SHA256)، وآمنة التكرار لكل حدث، وتُعاد محاولتها مع انتظار متزايد، ويمكن تدوير السرّ دون انقطاع.
الأحداث
| الحدث | متى |
|---|---|
payment.success | أكّد Orange Money سيراليون الدفع (أدخل العميل رمز PIN) |
payment.failed | رفض Orange Money الدفع أو أعاد خطأً أو انتهت مهلته |
payment.cancelled | ألغى الدافع العملية |
payment.expired | انقضت مهلة الدفع دون إتمامه |
payment.refunded | أصبحت المبالغ المستردة تغطي الدفعة كاملة (data.refund = عملية الاسترداد) |
payment.partially_refunded | اكتمل استرداد جزئي (data.refund = عملية الاسترداد) |
payout.success / payout.failed / payout.cancelled | نتيجة دفعة صادرة إلى محفظة Orange Money |
refund.success / refund.failed | نتيجة عملية استرداد (data = كائن refund + transaction_reference وmerchant_reference وis_test) |
payout_request.submitted / payout_request.approved / payout_request.rejected / payout_request.completed / payout_request.failed | دورة حياة طلب سحب من رصيدك |
تختار الأحداث التي تشترك فيها كل نقطة استقبال (لوحة التحكم ← Webhooks).
تُنتج كل عملية استرداد مجموعة أحداثها الخاصة: refund.success + payment.refunded / payment.partially_refunded، أو refund.failed. راجع الاسترداد (بالإنجليزية).
يُسلَّم الحدث webhook.test (المرسل من زر الاختبار في لوحة التحكم ومن POST /v1/webhooks/test) إلى كل نقاط الاستقبال النشطة بغضّ النظر عن اشتراكاتها؛ ولا يمكن الاشتراك فيه.
الإعداد
لوحة التحكم ← المطوّرون ← Webhooks ← Add endpoint
- URL: يجب أن يكون HTTPS عامًا.
- الأحداث: مجموعة فرعية من القائمة أعلاه.
- السرّ: يُولَّد عشوائيًا. احفظه على خادمك.
تُعلَّق نقطة الاستقبال تلقائيًا بعد عدد من الإخفاقات المتتالية (عتبة داخلية).
callback_url لكل معاملة
عند إنشاء دفعة (POST /v1/payments) أو دفعة صادرة (POST /v1/payouts) يمكنك تمرير callback_url اختياري بصيغة HTTPS. ترسل SOAMTAR إلى هذا العنوان كل أحداث تلك المعاملة، بنفس المحتوى ونفس التوقيع X-Soamtar-Signature ونفس إعادات المحاولة المطبّقة على نقاط الاستقبال لديك.
- يُستخدم في التوقيع سرّ أقدم نقطة استقبال webhook نشطة لديك في البيئة نفسها (sandbox أو live). لذا يجب أن تسجّل نقطة استقبال واحدة على الأقل؛ وإلا فلن يُستدعى
callback_url. - إذا كان
callback_urlمطابقًا لعنوان نقطة استقبال مسجّلة، فلا يُرسل الحدث مرتين.
محتوى الـ webhook
جسم JSON المُرسَل إلى عنوانك (التنسيق الافتراضي):
{
"event": "payment.success",
"created": "2026-10-21T09:41:12+00:00",
"data": {
"id": "0e85b3c2-7c4f-4f1d-9b3a-1c8a2d4e5f6a",
"reference": "TXN-1A2B3C4D-ABCDEF123456",
"merchant_reference": "ORDER-001",
"provider_reference": "CI190304.1139.A01071",
"amount": "150.00",
"fee_amount": "2.25",
"net_amount": "147.75",
"currency": "SLE",
"status": "success",
"is_test": false,
"paid_at": "2026-10-21T09:41:10+00:00",
"metadata": { "cart_id": "abc-123" }
}
}data.id هو معرّف معاملة SOAMTAR؛ وdata.merchant_reference هو قيمة merchant_ref التي أرسلتها.
التوقيع
يحمل كل طلب الترويسة:
X-Soamtar-Signature: t=<unix>,v1=<hex_hmac_sha256>أثناء تدوير السرّ، قد يتعايش توقيعان:
X-Soamtar-Signature: t=<unix>,v1=<hmac_current_secret>,v2=<hmac_next_secret>التحقق
- استخرج الطابع الزمني
tوالتوقيعv1(وv2إن وُجد). - تحقّق من أن
tحديث (ضمن نافذة التسامح). - أعد حساب
expected = HMAC-SHA256(t + "." + raw_body, secret). - قارن في زمن ثابت (
hash_equalsفي PHP، وcrypto.timingSafeEqualفي Node). - إن وُجد
v2، فاقبل إذا طابق v1 أو v2.
$header = $_SERVER['HTTP_X_SOAMTAR_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
$secrets = [getenv('SOAMTAR_WEBHOOK_SECRET'), getenv('SOAMTAR_WEBHOOK_SECRET_NEXT')];
if (! preg_match('/t=(\d+)/', $header, $tm)) throw new RuntimeException('bad sig');
$ts = (int) $tm[1];
if (abs(time() - $ts) > 300) throw new RuntimeException('expired');
$ok = false;
foreach (['v1', 'v2'] as $i => $version) {
if (! preg_match("/{$version}=([a-f0-9]{64})/", $header, $vm)) continue;
$secret = $secrets[$i] ?? null;
if ($secret === null || $secret === '') continue;
if (hash_equals(hash_hmac('sha256', "{$ts}.{$body}", $secret), $vm[1])) {
$ok = true; break;
}
}
if (! $ok) throw new RuntimeException('invalid');التسامح
يستخدم المثال 300 ثانية، وهو حد أعلى معقول يطابق هامش التسامح الذي تطبّقه SOAMTAR.
ترويسات إضافية
| الترويسة | المعنى |
|---|---|
X-Soamtar-Timestamp | الطابع الزمني للتوقيع (القيمة نفسها لـ t) |
X-Soamtar-Event | نوع الحدث (payment.success, ...) |
X-Soamtar-Delivery | معرّف محاولة الإرسال، مفيد في السجلات |
User-Agent | Soamtar-Webhooks/1.0 (+https://app.so-amtar.com/docs/webhooks) |
التكرار الآمن على خادمك
قد يصل الحدث نفسه أكثر من مرة (إعادة محاولة بعد انتهاء المهلة، إعادة إرسال بعد حادث). أزل التكرار بالاعتماد على الزوج (event، data.id) وتجاهل المكرَّر.
CREATE UNIQUE INDEX uq_inbound_event ON your_webhook_log(event, transaction_id);إعادة المحاولة والإرسال
- 5 محاولات كحد أقصى: الإرسال الأول ثم إعادة المحاولة بعد 60 ثانية و5 دقائق و30 دقيقة وساعتين.
- 2xx = نجاح، وتتوقف SOAMTAR.
- 3xx = يُعدّ إخفاقًا (يجب أن ترد نقطة الاستقبال بـ 2xx مباشرة).
- 4xx أو 5xx أو انتهاء المهلة = إخفاق، وتُجدوَل إعادة محاولة.
- بعد عدد من الإخفاقات المتتالية، تُعلَّق نقطة الاستقبال وتصلك رسالة بريد.
يمكنك إعادة إرسال عملية يدويًا من لوحة التحكم ← Webhook logs ← Resend.
تدوير السرّ دون انقطاع
- لوحة التحكم ← Webhooks ← Rotate secret. تعرض SOAMTAR السرّ الجديد (انسخه الآن: لن يُعرض مجددًا).
- خلال نافذة التدوير، قد تُوقَّع كل حمولة بالسرّين معًا.
- حدّث أداة التحقق لديك لتقبل
v1أوv2. - انقر على Promote: يصبح السرّ الجديد هو الحالي ويُبطَل السرّ القديم.
اختبار webhook
استخدم لوحة التحكم لإرسال حدث اختباري موقَّع بسرّك الحقيقي (لوحة التحكم ← Webhooks ← Test)، أو استدعِ POST /v1/webhooks/test بمفتاحك السرّي. يرسل كلاهما الحدث webhook.test، ويُسلَّم إلى كل نقاط الاستقبال النشطة بغضّ النظر عن اشتراكاتها.
إذا فاتك webhook
- الاستعلام النشط:
POST /v1/payments/{id}/verifyيجعل SOAMTAR تستعلم من Orange Money وتعيد إصدار الحدث إذا حدث الانتقال. - تسوية آلية داخلية تلتقط المعاملات العالقة: لا تحتاج عادةً إلى أي إجراء.