إشعارات ويب هوك
توقف عن الاستعلام المتكرر. نرسل رمز الرسالة إلى خادمك لحظة وصوله، موقعًا ومع إعادة محاولة.
لماذا ويب هوك
الاستعلام المتكرر يستهلك الطلبات ويضيف تأخيرًا. الويب هوك يصل إلى خادمك في نفس الثانية التي يصل فيها الرمز.
- لا حلقة استعلام: حصتك اليومية من الطلبات تذهب إلى العمل الحقيقي بدل فحص الحالة.
- زمن استجابة أقل: الرمز يصل إلى نظامك فور وصوله إلينا، لا عند استعلامك التالي.
- الأحداث النهائية أيضًا: الإلغاء وانتهاء المهلة يصلان مع حالة الاسترداد، فتبقى حساباتك متوافقة.
الإعداد
سجّل رابطًا واحدًا لكل حساب باستخدام مفتاح الواجهة البرمجية. يُعاد مفتاح التوقيع مرة واحدة فقط.
curl -X POST https://smsbulk.net/api/users/webhook \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://your-app.com/smsbulk/webhook"}'الاستجابة
{
"id": "wep_2f8c1a9d",
"url": "https://your-app.com/smsbulk/webhook",
"sourceFilter": "API",
"secret": "whsec_5f3a9c1e8b2d47a6903c5e1f7d8b4a26"
}احفظ المفتاح الآن
يظهر المفتاح الصريح في هذه الاستجابة فقط، والقراءات اللاحقة تعيد قيمة مقنّعة. إن فقدته فجدّده من لوحة التحكم أو عبر نقطة rotate.
شروط الرابط
- HTTPS على المنفذ 443. يُرفض HTTP العادي والمنافذ الأخرى.
- عنوان عام. النطاقات الخاصة وعناوين الاسترجاع وعناوين بيانات السحابة تُرفض عند التسجيل وعند كل عملية إرسال.
- لا إعادة توجيه. استجابة 3xx لا تُتبع وتُحسب محاولة فاشلة.
| الطريقة | المسار | الغرض |
|---|---|---|
| POST | /api/users/webhook | يسجل النقطة أو يستبدلها، ويعيد المفتاح مرة واحدة |
| GET | /api/users/webhook | يقرأ النقطة الحالية والمفتاح مقنّع |
| PATCH | /api/users/webhook | يغيّر الرابط أو مرشّح القناة، أو يعيد تفعيل نقطة معطّلة |
| POST | /api/users/webhook/rotate | يجدّد مفتاح التوقيع مع نافذة انتقالية |
| POST | /api/users/webhook/test | يرسل حدث اختبار لا يحمل بيانات تفعيل حقيقية |
| DELETE | /api/users/webhook | يحذف النقطة ويوقف كل الإشعارات |
| GET | /api/users/webhook/deliveries | آخر 20 محاولة إرسال، بيانات وصفية فقط |
الأحداث
ثلاثة أحداث، جميعها عن تفعيل واحد.
| الحدث | متى يُرسل |
|---|---|
| activation.code_received | عند وصول رمز الرسالة. هذا هو الحدث الذي تتعامل معه معظم التكاملات. |
| activation.cancelled | عند إلغاء التفعيل. يحمل الجسم سبب الإلغاء وما إذا كان الرصيد قد أُعيد. |
| activation.expired | عند انتهاء مهلة التفعيل دون وصول رمز. يحمل الجسم حالة الاسترداد. |
مرشّح القناة
افتراضيًا تُنشئ الإشعارات الطلبات المرسلة عبر الواجهة البرمجية فقط. اضبط sourceFilter على null إن أردت أيضًا الطلبات التي تنفذها من لوحة التحكم.
الطلب والجسم
كل إرسال هو POST بجسم JSON. تُختار الحقول واحدًا واحدًا: التوجيه الداخلي وتفاصيل التكلفة لا تظهر أبدًا.
شكل الطلب
POST /smsbulk/webhook HTTP/1.1
Content-Type: application/json
User-Agent: SMSBulk-Webhook/1
X-SMSBulk-Signature: t=1785312000,v1=8f2a...c41d
X-SMSBulk-Event-Id: evt_del_9f2c1a
X-SMSBulk-Event-Type: activation.code_received
X-SMSBulk-Delivery-Attempt: 1activation.code_received
{
"id": "evt_del_9f2c1a",
"type": "activation.code_received",
"created": 1785312000,
"data": {
"activation_id": "cms2u6x80d1twmkudp2cfbqo4",
"status": "RECEIVED",
"phone_number": "447700900123",
"service": "wa",
"country": "GB",
"code": "483920",
"price": "0.42",
"currency": "USD",
"source": "API",
"created_at": "2026-08-04T09:12:00.000Z",
"sms_text": "Your code is 483920",
"received_at": "2026-08-04T09:12:34.000Z"
}
}activation.cancelled و activation.expired
{
"id": "evt_del_7b31de",
"type": "activation.cancelled",
"created": 1785312600,
"data": {
"activation_id": "cms2u6x80d1twmkudp2cfbqo4",
"status": "CANCELLED",
"phone_number": "447700900123",
"service": "wa",
"country": "GB",
"code": null,
"price": "0.42",
"currency": "USD",
"source": "API",
"created_at": "2026-08-04T09:12:00.000Z",
"reason": "user_cancel",
"refunded": true,
"refund_amount": "0.42"
}
}قد تُضاف حقول جديدة في إصدارات لاحقة. تجاهل الحقول غير المعروفة بدل الفشل بسببها.
التحقق من التوقيع
كل طلب يحمل توقيع HMAC SHA256 مبنيًا على الطابع الزمني والجسم الخام.
X-SMSBulk-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256>
signed_string = "<t>.<raw_request_body>"تحقق من البايتات الخام
احسب التوقيع على الجسم الذي استلمته تمامًا. إن حللت JSON ثم أعدت تحويله إلى نص فسيتغير ترتيب المفاتيح والمسافات ولن يتطابق التوقيع.
قد يوجد أكثر من v1
أثناء تجديد المفتاح تحمل الترويسة قيمتي v1. اجمعهما في قائمة لا في قاموس: القاموس يحتفظ بالأخيرة فقط ويبقى المفتاح الجديد دون تحقق بصمت.
Node.js
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const pairs = header.split(',').map((p) => {
const i = p.indexOf('=');
return [p.slice(0, i), p.slice(i + 1)];
});
const t = pairs.find(([k]) => k === 't')?.[1];
// During a rotate window more than one signature arrives: collect them all.
const signatures = pairs.filter(([k]) => k === 'v1').map(([, v]) => v);
if (!t || signatures.length === 0) return false;
// Replay window: reject anything older than 5 minutes.
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return signatures.some(
(s) =>
s.length === expected.length &&
crypto.timingSafeEqual(
Buffer.from(s, 'hex'),
Buffer.from(expected, 'hex'),
),
);
}Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
pairs = [p.split("=", 1) for p in header.split(",")]
t = next((v for k, v in pairs if k == "t"), None)
# During a rotate window more than one signature arrives: collect them all.
signatures = [v for k, v in pairs if k == "v1"]
if t is None or not signatures:
return False
# Replay window: reject anything older than 5 minutes.
if abs(time.time() - int(t)) > 300:
return False
expected = hmac.new(
secret.encode(),
f"{t}.".encode() + raw_body,
hashlib.sha256,
).hexdigest()
return any(hmac.compare_digest(s, expected) for s in signatures)الطابع الزمني جزء من النص الموقّع، لذا يمكنك رفض ما مضى عليه أكثر من بضع دقائق. قارن التواقيع بزمن ثابت.
الاستجابة وإعادة المحاولة
استجب بسرعة. ننتظر الرد 10 ثوانٍ.
افعل
- أعد 2xx فور حفظ الحدث.
- ضع العمل الثقيل في طابور وأجب أولًا.
- اعتبر 4xx من طرفك خللًا: فهو يوقف إعادة المحاولة.
لا تفعل
- لا تشغّل مهامًا طويلة قبل الرد، فالطلب ينتهي بعد 10 ثوانٍ.
- لا ترد بـ 3xx، فإعادة التوجيه لا تُتبع.
- لا تبنِ جسم استجابة كبيرًا، فهو يُهمل دون قراءة.
| المحاولة | الانتظار قبلها | activation.code_received | cancelled و expired |
|---|---|---|---|
| 1 | فورًا | ||
| 2 | 10s | ||
| 3 | 60s | ||
| 4 | 5m | ||
| 5 | 15m | ||
| 6 | 30m |
تحدث إعادة المحاولة عند أخطاء الشبكة والمهلة و408 و429 و5xx. أي 4xx آخر يوقف السلّم فورًا. يتوقف code_received عند 4 محاولات لأن التفعيل ينتهي قبل أن تفيد الخطوات التالية.
منع التكرار
استخدم X-SMSBulk-Event-Id مفتاحًا للتكرار الآمن. محاولات نفس الحدث تحمل المعرّف ذاته.
مرة واحدة على الأقل، لا مرة واحدة تمامًا
يُنشأ سجل واحد لكل تفعيل ونوع حدث، لذا التكرار نادر. ومع ذلك قد تعيد استعادة مهمة متعثرة إرسال السجل نفسه. احفظ معرّف الحدث وتجاهل معرّفًا عالجته من قبل.
قاطع الدارة
النقطة المعطّلة لا تُستدعى إلى الأبد.
- بعد 20 عملية إرسال فاشلة متتالية تُعطّل النقطة.
- تصلك رسالة بريد، وتعرض لوحة التحكم سبب التعطيل.
- الأحداث التي تنشأ أثناء التعطيل لا تُعاد. عوّضها عبر GET /v1/activations.
تجديد المفتاح
غيّر مفتاح التوقيع دون انقطاع.
- تعيد نقطة rotate المفتاح الجديد مرة واحدة.
- خلال النافذة الانتقالية يُرسل التوقيعان الجديد والسابق معًا.
- انتقل إلى المفتاح الجديد بوتيرتك، وبعد النافذة يُرسل التوقيع الجديد وحده.
أعدّ نقطتك
سجّل رابطًا، أرسل حدث اختبار، وتابع قائمة الإرسال من لوحة التحكم.
