نظرة عامة على Webhooks
كيف يقدّم ThunderPhone الأحداث في الوقت الفعلي، وكيفية التحقق من التوقيعات، ومقارنة نماذج التسليم القديمة والقائمة على نقاط النهاية.
يرسل ThunderPhone طلبات HTTP POST إلى خادمك عند حدوث أمور
أثناء مكالمة — بدء مكالمة واردة، وانتهاء مكالمة، واكتمال
تشغيل تقييم، وإطلاق تنبيه، وغير ذلك. يوجد نموذجان للتسليم:
عناوين URL متعددة، وأسرار لكل نقطة نهاية، ومرشحات أحداث لكل نقطة نهاية،
وإعادات محاولة تلقائية.
أدِرها عبر GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
عنوان URL واحد لكل مؤسسة. ينقل أحداث دورة حياة المكالمة، بما في ذلك
تبادلات الإعداد المعطِّلة. يُدار عبر GET/PUT /v1/webhook.
تُسلَّم أنواع الأحداث العشرة كلها في كتالوج الأحداث
عبر نقاط نهاية webhook. تُرسل أحداث دورة حياة المكالمة الستة
(telephony.incoming وtelephony.complete وtelephony.tool و
web.incoming وweb.complete وweb.tool) أيضًا إلى
webhook القديم ذي عنوان URL الواحد — إذا كان لديك عنوان URL قديم ونقطة
نهاية مطابقة، فستتلقى الحدث عبر كلا المسارين. يوجد السلوك المعطِّل
(تبادل إعداد telephony.incoming / web.incoming
ووضع webhook الخاص بإرسال الأدوات)
حصريًا في المسار القديم؛ أما كل عملية تسليم إلى نقطة نهاية فهي إشعار
إرسال دون انتظار.
تنسيق الحمولة
تكون عمليات التسليم إلى نقاط النهاية كائن JSON يحتوي على data وevent_id و
type:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}يكون event_id فريدًا لكل حدث مُصدَر. وهو متطابق عبر إعادات المحاولة
وعبر كل نقطة نهاية تتلقى الحدث — أزل التكرار بناءً عليه.
يرسل webhook القديم ذو عنوان URL الواحد القيمتين type وdata نفسيهما، ولكن
من دون event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}عبر الشبكة، تُسلسَل كل حمولة بشكل قياسي — تُرتَّب المفاتيح أبجديًا، ومن دون مسافات بيضاء، وبترميز UTF-8. أمثلة التنسيق الجميل في هذه الوثائق مخصّصة لسهولة القراءة فقط.
راجع كتالوج الأحداث للحصول على القائمة الكاملة لأنواع الأحداث وحقول الحمولة.
التحقق من التوقيع
يحمل كل طلب توقيع HMAC-SHA256 على النص الخام لجسم
الطلب في الترويسة X-ThunderPhone-Signature. مفتاح التوقيع هو secret
الخاص بنقطة النهاية (أو secret الخاص بخطاف الويب على مستوى مؤسستك لعمليات
التسليم القديمة).
الخطوات
- اقرأ جسم الطلب الخام قبل أي تحليل.
- احسب
hmac_sha256(secret, body).hexdigest(). - قارنه في وقت ثابت بترويسة
X-ThunderPhone-Signature.
نوقّع البايتات التي نرسلها بالضبط، وهذه البايتات هي تمثيل JSON القياسي (مفاتيح مرتبة وفواصل مضغوطة). لذلك يعمل التحقق باستخدام الجسم الخام دائمًا — وإذا كان إطار عملك لا يزوّدك إلا بـ JSON محلل، فإن إعادة تسلسله بمفاتيح مرتبة وفواصل مضغوطة تنتج البايتات المطابقة. كلا الطريقتين مغطاتان في دليل التحقق.
import hmac
import hashlib
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature or "")
# Example Flask handler
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/thunderphone-webhook")
def handle():
body = request.get_data()
sig = request.headers.get("X-ThunderPhone-Signature", "")
if not verify_signature(body, sig, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
# dispatch on event["type"] …
return "", 204import crypto from "node:crypto";
import express from "express";
function verifySignature(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
if (!signature || expected.length !== signature.length) return false;
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}
const app = express();
app.post(
"/thunderphone-webhook",
express.raw({ type: "application/json" }),
(req, res) => {
const sig = req.header("X-ThunderPhone-Signature") || "";
if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString("utf8"));
// dispatch on event.type …
res.sendStatus(204);
},
);دلالات التسليم
تنطبق هذه الدلالات على عمليات التسليم إلى نقاط النهاية. يمثل الويب هوك القديم ذو عنوان URL الواحد محاولة متزامنة واحدة دون إعادات محاولة.
إعادات المحاولة
تتم محاولة كل حدث مرة واحدة فورًا. تؤكد أي استجابة 2xx
عملية التسليم. عند أي نتيجة أخرى (غير 2xx،
أو خطأ في الاتصال، أو انتهاء المهلة)، نعيد المحاولة بعد 1 m و5 m و30 m و2 h و6 h
و12 h و24 h من المحاولة الأولى — أي 8 محاولات تمتد عبر
24 ساعة. إذا فشلت كل المحاولات، يتوقف التسليم وتُعلَّم نقطة النهاية
بالقيمة status="failing" في
نقاط نهاية الويب هوك. أرجع 2xx بمجرد
قبول الحمولة بشكل دائم؛ وعالجها بصورة غير متزامنة.
الترتيب
ترتيب التسليم يعتمد على أفضل جهد. عمليًا، نسلّم الأحداث بالترتيب الذي
تُصدَر به، لكن إعادات المحاولة قد تعيد ترتيبها عند الفشل.
أزل التكرار دائمًا وسوِّ البيانات باستخدام call_id / معرّف الكائن.
التكرارات
يتم التسليم مرة واحدة على الأقل: قد تؤدي إعادة المحاولة بعد استجابة لم
نرها إلى تكرار حدث. تحمل كل إعادة محاولة القيمة نفسها
event_id، لذا خزّن المعرّفات التي تمت معالجتها وتجاوز التكرارات. تتم مشاركة event_id
أيضًا بين نقاط النهاية — تتلقى نقطتا نهاية مشتركتان في
الحدث نفسه القيمة نفسها event_id.
انتهاء المهلات
تتضمن عمليات التسليم إلى نقاط النهاية مهلة قدرها 30 s لكل محاولة. في
المسار القديم، تنتهي مهلة الطلبات الحاجبة التي تتحكم في سلوك المكالمات المباشرة —
تبادل الإعدادات telephony.incoming / web.incoming
— بعد 10 s، لكن الاستجابة البطيئة تؤخر الرد على المكالمة،
لذا استهدف الرد خلال بضع ثوانٍ. يتيح إرسال الأدوات في
وضع الويب هوك مدة 20 s افتراضيًا، ويمكن لتعريفات الأدوات ضبط timeout
على المستوى الأعلى.
عناوين IP المصدر
تنشأ عمليات الويب هوك الصادرة من نطاق عناوين IP السحابية لـ ThunderPhone. إذا كان جدار الحماية لديك يتطلب قائمة سماح، فاتصل بالدعم وسنشاركك النطاقات الحالية.
الاختيار بين الويب هوك القديم والويب هوك المستند إلى نقاط النهاية
| الميزة | القديم (/v1/webhook) | نقاط النهاية (/v1/developer/webhook-endpoints) |
|---|---|---|
| عدد عناوين URL | 1 لكل مؤسسة | عدة عناوين لكل مؤسسة |
| تغطية الأحداث | telephony.* / web.* فقط | جميع أنواع الأحداث العشرة |
| تصفية الأحداث | — | لكل نقطة نهاية |
| إعادات المحاولة | لا يوجد | 8 محاولات خلال 24 h |
| الغلاف | type + data | type + data + event_id |
| تدوير السر | يستبدل السر الوحيد | سر لكل نقطة نهاية |
| التعطيل دون حذف | — | status=disabled |
| عرض الحالة | — | active / disabled / failing |
| تبادل الإعدادات الحاجب | نعم (telephony.incoming / web.incoming) | أبدًا — إشعارات فقط |
| الأنسب لـ | إعداد المكالمات الديناميكي | استهلاك الأحداث في بيئة الإنتاج |
ينبغي لعمليات التكامل الجديدة استهلاك الأحداث عبر الويب هوك المستند إلى نقاط النهاية. احتفظ بعنوان URL قديم (أو أضفه) فقط إذا كنت تضبط المكالمات بشكل ديناميكي عند وقت الرد أو تستخدم إرسال الأدوات في وضع الويب هوك — إذ إن عمليات تبادل الطلبات والاستجابات هذه تعمل فقط عبر المسار القديم.