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

Webhooks

telephony.incoming / web.incoming

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

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

المهلات وإعادات المحاولة

أجب بسرعة: المتصل ينتظر على الخط بينما تتخذ قرارك.

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

  • قد تستغرق المحاولة الأولى ما يصل إلى 10 ثوانٍ.
  • إذا انتهت مهلتها، أو تعذر الاتصال، أو تلقّت استجابة 5xx أو 408 أو 429، يحاول ThunderPhone مرة أخرى. تحصل إعادة المحاولة على ما تبقى من ميزانية قدرها 13 ثانية للتبادل بأكمله: نحو 3 ثوانٍ بعد محاولة أولى انتهت مهلتها، وما يقارب 13 ثانية كاملة بعد فشل سريع. لا تتم إعادة المحاولة إذا كان سيتبقى أقل من ثانيتين.
  • أي استجابة 4xx أخرى، أو استجابة تتجاوز حد الحجم، أو استجابة 2xx لا يحتوي نصها على إعداد صالح، تكون نهائية ولا تتم إعادة محاولتها.
  • ترسل كلتا المحاولتين النص نفسه، وX-ThunderPhone-Signature نفسه، وترويسة X-ThunderPhone-Idempotency-Key نفسها (telephony-incoming-<call_id>)، بحيث يمكن لمعالج له آثار جانبية التعرّف على إعادة المحاولة.

لا يوجد وكيل صوتي بديل. إذا لم يصل إعداد قابل للاستخدام، تُنهى المكالمة فورًا بدلًا من ترك المتصل على خط صامت. تظهر في سجل المكالمات كمكالمة فاشلة غير مفوترة مع end_reason بقيمة inbound_config_webhook_failed، ويُرسل حدث telephony.complete المعتاد.

تحصل جلسة عنصر واجهة (web.incoming) على محاولة واحدة تصل مدتها إلى 10 ثوانٍ. إذا فشلت، يفشل طلب الجلسة بالرمز 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سلسلة نصيةموجود عند تمرير الودجة لسياق الصفحة لكل جلسة

تصل حقول التجاوز (language وvoice وwebsite_context وأي variables للمتصفح) فقط عندما يسمح بها المفتاح القابل للنشر في تجاوزات المتصفح الخاصة به. يُرفض طلب الجلسة الذي يتضمن تجاوزاً لا يسمح به المفتاح قبل استدعاء هذا الخطاف الشبكي.


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

أعِد كائن 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. عند انتقال المكالمة إلى لغة أخرى، بدّل إلى صوت أصلي لتلك اللغة (مطابق من حيث الجنس) بدلًا من الاحتفاظ بالصوت المهيأ
language_switchingسلسلة نصية | فارغلاask_first (الافتراضي) أو follow_caller؛ القيمة null مماثلة لـ ask_first. يحدد كيفية تعامل وكيل متعدد اللغات مع شخص آخر يتحدث لغة أخرى من لغاته. يبدّل ask_first فورًا عندما يطلبون اللغة أو يقولون جملة كاملة بها، ويسأل عما إذا كانوا يريدون المتابعة بها بعد قول شيء قصير، مثل «نعم». يبدّل follow_caller إلى أي لغة مهيأة يتحدثون بها، حتى لو كانت كلمة واحدة. ينطبق فقط عند تعيين additional_languages
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
pronunciation_dictsمنطقي | فارغلاالقيمة الافتراضية هي false؛ القيمة null مماثلة لـ false. عندما تكون القيمة true، يستخدم نطق Cartesia في المكالمة قاموس النطق الخاص بـ ThunderPhone للغة المكالمة، عند توفره. يعمل مع مكالمات الهاتف والأداة المصغرة. ينطبق فقط على أصوات Cartesia الخاصة بـ ThunderPhone، وليس على الأصوات التي تستخدم مفتاح Cartesia API الخاص بك. لا يُستخدم مع agent_id، حيث تُطبّق إعدادات الوكيل المحفوظة
voicemail_actionسلسلة نصيةلاprompt (الافتراضي) أو hangup أو message
voicemail_messageسلسلة نصيةلايُستخدم عندما تكون voicemail_action="message"
agent_nameسلسلة نصيةلااسم العرض المُبلّغ عنه للوحات المعلومات والأداة المصغرة
org_nameسلسلة نصيةلااسم العرض للمؤسسة لشخصية الوكيل
toolsمصفوفةلامخططات أدوات الوظائف المضمنة (راجع أدوات الوظائف)
transfer_destinationsمصفوفة من الكائنات | فارغلاما يصل إلى 50 إدخالًا من {name, number} يمكن لهذه المكالمة التحويل إليها، ويجري التحقق منها مثل أرقام التحويل الخاصة بالوكيل. يحوّل الوكيل بالاسم وإلى هذه الإدخالات فقط؛ تعني [] عدم وجود تحويلات. احذفها أو أرسل null للسماح للوكيل بالتحويل إلى رقم يختاره
timezoneسلسلة نصيةلامنطقة زمنية من IANA لهذه المكالمة، مثل America/Chicago. يذكر الوكيل التاريخ والوقت وفقًا لها بدلًا من منطقته الزمنية الخاصة. يُسمح بها أيضًا إلى جانب agent_id. تُتجاهل القيم التي ليست مناطق زمنية من IANA. راجع تعيين المنطقة الزمنية لكل مكالمة
call_idعدد صحيحلاإعادة اختيارية لمعرّف المكالمة في الطلب؛ تُتجاهل

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


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


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

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أعلىعميقنص تلقائي أثناء التفكير

ذو صلة