תצורה דינמית לכל שיחה
בחרו את הסוכן שיענה — או שכתבו מחדש את ההנחיה וההגדרות שלו — בנפרד עבור כל שיחה נכנסת, על בסיס לוגיקה מותאמת אישית ב-webhook שבשליטתכם.
כברירת מחדל, לכל מספר טלפון ולכל מפתח ניתן לפרסום מוקצה סוכן קבוע. כשנדרשת התאמה אישית לכל מתקשר או לכל מבקר — ניתוב VIP, הקשר של משתמש מחובר, בדיקות הנחיה מסוג A/B — עברו למצב וובהוק ואפשרו לשרת שלכם להחליט.
איך זה עובד
- הירשמו לאירוע
telephony.incoming(טלפון) אוweb.incoming(וידג'ט). שניהם וובהוקים חוסמים: ThunderPhone ממתינה עד 10 שניות לתגובה שלכם לפני המשך השיחה. - ThunderPhone שולחת לכם את
{call_id, from_number, to_number}(הפעלות וידג'ט כוללות שדות ייעודיים לווידג'ט במקום מספרים — ראו את סכמת הבקשה). - השרת שלכם משיב עם תצורת סוכן (הנחיה, קול, מוצר, כלים). ThunderPhone משתמשת בתצורה זו עבור השיחה.
- אם תחזירו
{}, אם תתרחש חריגה מזמן ההמתנה או שגיאה, ייעשה שימוש בסוכן שהוקצה באופן קבוע כגיבוי. ברירת מחדל בטוחה.
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). אל תדלגו על כך בסביבת פיתוח — הגדירו זאת נכון פעם אחת והשתמשו בזה מחדש.
- הגיבו במהירות. עשר שניות הן הגבול הקשיח, וכל שנייה היא זמן מת עבור המתקשר. בצעו חיפושי מסד נתונים אם צריך, אבל אל תקראו למודלי LLM במורד הזרם באופן סינכרוני — אם אתם רוצים יצירת הנחיות דינמית, חשבו מראש ושמרו במטמון.
- השתמשו בחלופה בצורה נקייה. כל מצב בלתי צפוי צריך להחזיר
{}כדי שהסוכן שהוקצה באופן סטטי יטפל בשיחה.
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 ...
passimport 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() במטפל שלכם.