ThunderPhone 2.0 متاح الآن.خدمة ذاتية، ابتداءً من 2 سنت/دقيقة.اقرأ الإعلان

Webhooks

telephony.incoming / web.incoming

خطاف ويب حاجب يضبط إعدادات مكالمة واردة في الوقت الفعلي.

عندما تصل مكالمة هاتفية واردة إلى رقم من دون وكيل مُعيّن، أو تبدأ جلسة عنصر واجهة ويب على مفتاح قابل للنشر في mode="webhook"، يرسل ThunderPhone طلب telephony.incoming / web.incoming حاجبًا إلى عنوان URL القديم للويب هوك وينتظر ما يصل إلى 10 ثوانٍ للحصول على استجابة إعداد. استخدم هذا التبادل لاختيار موجّه وصوت وأدوات لكل مكالمة ديناميكيًا — راجع دليل الإعداد الديناميكي للمكالمات للاطلاع على النمط المتكامل.

ليس للتبادل الحاجب بديل احتياطي: إذا أعاد المعالج حالة غير 2xx، أو انتهت مهلته، أو أعاد إعدادًا يفشل التحقق من صحته، تُرفض المكالمة (لا تتصل المكالمة الهاتفية؛ ويفشل طلب جلسة عنصر الواجهة بالرمز 502/422). استجب بسرعة — يسمع المتصل نغمة الرنين أثناء اتخاذك القرار.

حمولة الطلب

للمكالمات الهاتفية (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
الحقلالنوعالوصف
call_idعدد صحيحمعرّف المكالمة — ثابت عبر جميع أحداث هذه المكالمة
from_numberسلسلة نصيةرقم المتصل بتنسيق E.164
to_numberسلسلة نصيةوجهة E.164 (أحد أرقام ThunderPhone الخاصة بك)

بالنسبة إلى جلسات عنصر واجهة الويب (web.incoming)، تحدد data الصفحة المضمّنة بدلًا من أرقام الهاتف:

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
الحقلالنوعالوصف
call_idعدد صحيحمعرّف المكالمة
origin_domainسلسلة نصيةمصدر الصفحة التي تستضيف عنصر الواجهة
publishable_key_prefixسلسلة نصيةالأحرف الأولى من المفتاح القابل للنشر الذي فتح الجلسة
language, primary_languageسلسلة نصيةيظهران عندما تطلب جلسة عنصر الواجهة تجاوزًا للغة
voiceسلسلة نصيةيظهر عندما تطلب جلسة عنصر الواجهة تجاوزًا للصوت
website_contextسلسلة نصيةيظهر عندما يمرر عنصر الواجهة سياق الصفحة لكل جلسة

مخطط الاستجابة

أعِد كائن JSON يصف إعدادات الوكيل لهذه المكالمة. الحقلان prompt وvoice مطلوبان؛ وكل ما عداهما اختياري.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
الحقلالنوعمطلوبالوصف
promptسلسلة نصيةنعمموجّه النظام الذي يدير الوكيل
voiceسلسلة نصيةنعممعرّف الصوت من GET /v1/voices، مثل john. يُقبل voice_name كاسم مستعار. تفشل الأصوات غير المعروفة في التحقق وتُرفض المكالمة
productسلسلة نصيةلاالقيمة الافتراضية هي spark. القيم المسموح بها: spark وbolt وstorm-base وstorm-base-with-ack وstorm-extra وstorm-extra-with-ack
thinking_levelسلسلة نصيةلاminimal أو base (الافتراضي) أو extra. يُتجاوز لمنتجات Storm: يفرض storm-extra* القيمة extra، وتفرض منتجات storm-* الأخرى القيمة base
audio_context_modeسلسلة نصيةلاfull (الافتراضي) أو reduced
watchdog_enabledمنطقيلافعّل الإشراف لهذه المكالمة. القيمة الافتراضية false
additional_audio_contextمنطقي | فارغلاتضمين آخر بضعة أدوار من صوت المتصل بدلًا من الدور الأحدث فقط، مما يحسّن التصحيحات وجمع البيانات التي تكثر فيها التهجئة والأرقام، مع زيادة طفيفة في زمن الاستجابة والتكلفة. يكون مفعّلًا افتراضيًا للجلسات الواردة ومعطّلًا للمكالمات الهاتفية الصادرة؛ وتحافظ القيمة null على الإعداد الافتراضي
storm_feedback_modeسلسلة نصيةلاnone أو acknowledgement (الافتراضي) أو tick
languageسلسلة نصيةلااختصار لـ primary_language
primary_languageسلسلة نصيةلارمز اللغة، مُطبّعًا (الافتراضي en). ترفض الرموز غير القابلة للحل المكالمة
has_additional_languagesمنطقيلاالقيمة الافتراضية false
additional_languagesمصفوفة من السلاسل النصيةلالغات إضافية قد ينتقل الوكيل إليها
native_voice_switchingمنطقيلاالقيمة الافتراضية false. عند انتقال المكالمة إلى لغة أخرى، بدّل إلى صوت أصلي لتلك اللغة (مطابق للجنس) بدلًا من الاحتفاظ بالصوت المُعدّ
background_trackسلسلة نصية | فارغلامعرّف الصوت المحيط أو null
acknowledgement_prompt_modeسلسلة نصيةلاauto (الافتراضي) أو manual (لمنتجات Storm المزودة بالإقرار)
acknowledgement_promptسلسلة نصيةلايُستخدم عندما يكون acknowledgement_prompt_mode="manual"
silence_interval_secondsعدد صحيح | فارغلا5–120. ثواني صمت المتصل قبل إجراء تحقق
silence_max_checkinsعدد صحيح | فارغلا1–10
silence_checkins_enabledمنطقيلاالقيمة الافتراضية true
connect_tone_enabledمنطقيلاالقيمة الافتراضية false
voicemail_actionسلسلة نصيةلاprompt (الافتراضي) أو hangup أو message
voicemail_messageسلسلة نصيةلايُستخدم عندما يكون voicemail_action="message"
agent_nameسلسلة نصيةلااسم العرض الذي يُبلّغ به إلى لوحات المعلومات والأداة
org_nameسلسلة نصيةلااسم عرض المؤسسة لشخصية الوكيل
toolsمصفوفةلامخططات أدوات الدوال المضمنة (راجع أدوات الدوال)
call_idعدد صحيحلاتكرار اختياري لمعرّف المكالمة في الطلب؛ يُتجاهل

بما أن prompt وvoice مطلوبان، فإن إعادة {} أو أي استجابة تفشل في التحقق ترفض المكالمة بالرمز 422 — لا توجد آلية احتياطية لوكيل ثابت في هذا المسار (فلا يكون للرقم أو المفتاح في وضع خطاف الويب وكيل معيّن).

حد حجم الاستجابة


مثال على معالج

Python (FastAPI)
import hashlib
import hmac
import json
import os
 
from fastapi import FastAPI, HTTPException, Request
 
app = FastAPI()
WEBHOOK_SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
 
def verify(body: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")
 
@app.post("/thunderphone-webhook")
async def webhook(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(status_code=401)
 
    event = json.loads(body)
    if event["type"] == "telephony.incoming":
        caller = event["data"]["from_number"]
        prompt = (
            "Greet the caller as a San Francisco local…"
            if caller.startswith("+1415")
            else "You are a friendly customer support agent…"
        )
        return {
            "prompt": prompt,
            "voice": "john",
            "product": "spark",
        }
    if event["type"] == "web.incoming":
        return {
            "prompt": "You are the website's helpful voice assistant…",
            "voice": "john",
            "product": "spark",
        }
    return {}
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
 
const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;
 
function verify(body, signature) {
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(body)
    .digest("hex");
  return signature &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
 
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
 
    if (event.type === "telephony.incoming" || event.type === "web.incoming") {
      const caller = event.data.from_number || "web";
      const prompt = caller.startsWith("+1415")
        ? "Greet the caller as a San Francisco local…"
        : "You are a friendly customer support agent…";
      return res.json({
        prompt,
        voice: "john",
        product: "spark",
      });
    }
    res.json({});
  },
);

استجابة تتضمن أدوات دوال

أرفق أدوات كي يتمكن الذكاء الاصطناعي من استدعاء واجهات برمجة التطبيقات الخاصة بك أثناء المحادثة:

{
  "prompt":  "You are a booking assistant. Use the available tools to help customers schedule appointments.",
  "voice":   "john",
  "product": "spark",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": {
          "X-Api-Key": "your-key"
        }
      }
    }
  ]
}

دليل سريع لمستويات المنتجات

المنتجزمن الاستجابةالاستدلالالإقرار
sparkالأدنىأساسي
boltمنخفضمحسّن
storm-baseمتوسطقوي
storm-base-with-ackمتوسطقوينص تلقائي أثناء التفكير
storm-extraأعلىعميق
storm-extra-with-ackأعلىعميقنص تلقائي أثناء التفكير

ذو صلة