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

Developer cookbook

تهيئة ديناميكية لكل مكالمة

اختر الوكيل الذي يرد — أو أعد كتابة موجّهه وإعداداته — بشكل منفصل لكل مكالمة واردة، استنادًا إلى منطق مخصص في خطاف ويب تتحكم فيه.

افتراضيًا، يُعيَّن وكيل ثابت لكل رقم هاتف ومفتاح قابل للنشر. عندما تحتاج إلى تخصيص لكل متصل أو لكل زائر — توجيه كبار العملاء، وسياق المستخدم الذي سجّل الدخول، واختبارات موجّهات A/B — انتقل إلى وضع خطاف الويب ودع خادمك يقرر.

آلية العمل

  1. اشترك في حدث telephony.incoming (الهاتف) أو web.incoming (الأداة المصغرة). كلاهما خطافات ويب حاجبة: ينتظر ThunderPhone ما يصل إلى 10 ثوانٍ لاستجابتك قبل متابعة المكالمة.
  2. يرسل إليك ThunderPhone {call_id, from_number, to_number} (تحمل جلسات الأداة المصغرة حقولًا خاصة بها بدلًا من الأرقام — راجع مخطط الطلب).
  3. يستجيب خادمك بتكوين وكيل (الموجّه، الصوت، المنتج، الأدوات). يستخدم ThunderPhone ذلك التكوين للمكالمة.
  4. إذا أعدت {}، أو انتهت المهلة، أو حدث خطأ، يُستخدم الوكيل المعيّن ثابتًا كخيار احتياطي. إعداد افتراضي آمن.

1. ضبط وجهة خطاف الويب

بالنسبة إلى أرقام الهاتف، اشترك بنقطة نهايتك في telephony.incoming:

curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'

تتضمن الاستجابة secret يُستخدم مرة واحدة — احفظه؛ ستستخدمه للتحقق من التوقيع.

بالنسبة إلى جلسات الأداة المصغرة، أنشئ مفتاحًا قابلًا للنشر في mode="webhook" مع تضمين عنوان URL لنقطة النهاية:

curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'

سترسل الأداة المصغرة طلب POST إلى عنوان URL هذا عند بدء كل جلسة.

2. نفّذ المعالج

ثلاث قواعد عملية:

  • تحقّق من التوقيع في كل طلب (راجع التحقق من توقيعات webhook). لا تتجاوز هذه الخطوة في بيئة التطوير — نفّذها بشكل صحيح مرة واحدة وأعد استخدامها.
  • استجب بسرعة. عشر ثوانٍ هي الحد الأقصى الصارم، وكل ثانية هي صمت تام للمتصل. نفّذ عمليات البحث في قاعدة البيانات عند الحاجة، لكن لا تستدعِ نماذج اللغة الكبيرة التابعة بشكل متزامن — إذا أردت إنشاء موجّهات ديناميكية، فاحسبها مسبقًا وخزّنها مؤقتًا.
  • ارجع إلى الإعداد الاحتياطي بسلاسة. يجب أن تعيد أي حالة غير متوقعة {} لكي يتولى الوكيل المعيّن ثابتًا معالجة المكالمة.
FastAPI
import hashlib
import hmac
import json
import os
 
from fastapi import FastAPI, HTTPException, Request
 
app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
 
def verify(body: bytes, sig: str) -> bool:
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig or "")
 
@app.post("/thunderphone/incoming")
async def incoming(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(401)
 
    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default
 
    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs
 
def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
Express
import crypto from "node:crypto";
import express from "express";
 
const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;
 
function verify(body, sig) {
  const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
  return sig &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
 
app.post(
  "/thunderphone/incoming",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
 
    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});
 
    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);

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

يطابق نص الاستجابة مخطط استجابة المكالمة الواردة تمامًا. الحقول شائعة الاستخدام:

الحقلالنوعالوصف
promptسلسلة نصية (مطلوب)الموجّه النظامي للوكيل
voiceسلسلة نصية (مطلوب)معرّف الصوت من GET /v1/voices
productسلسلة نصيةالقيمة الافتراضية هي spark
background_trackسلسلة نصية | nullمعرّف الصوت المحيط
acknowledgement_prompt_modeسلسلة نصيةauto أو manual (Storm مع الإقرار فقط)
acknowledgement_promptسلسلة نصيةمطلوب عندما يكون الوضع manual
toolsمصفوفةمخططات أدوات الوظائف المضمنة — راجع أدوات الوظائف

أنماط

سياق المستخدم المسجّل الدخول

في الودجات بوضع webhook، تعرف صفحة الزائر هويته بالفعل. استدعِ webhook الخاص بك باستخدام معلمة سلسلة استعلام يمررها SDK الودجت (?customer_id=123) وابحث عن العميل من جهة الخادم.

طرح موجّهات A/B تدريجيًا

قبل تنفيذ ذلك يدويًا، لاحظ أن ThunderPhone يوفر ميزة التجارب الأصلية (/dashboard/experiments وعلامة التبويب A/B في أداة إنشاء الوكيل) التي تعرّف المتغيرات، وتقسم الزيارات، وتقارن النتائج لكل متغير — من دون الحاجة إلى webhook.

إذا كنت تحتاج إلى التحكم من جهة webhook على أي حال: أنشئ تجزئة لـ call_id → مجموعة؛ وقدّم الموجّه A للقيم 0..49 والموجّه B للقيم 50..99. سجّل المجموعة التي اخترتها في قاعدة بياناتك الخاصة، ثم اربطها لاحقًا بتقييم المكالمة المكتملة.

توجيه قائم على الوقت

ساعات العمل → وكيل "دعم مباشر"؛ خارج ساعات العمل → وكيل "تلقّي رسالة". استخدم تبديلًا بسيطًا على new Date().getUTCHours() في معالجك.


الخطوات التالية