Open in
telephony.incoming / web.incoming
Webhook חוסם שמעצב את תצורת השיחה הנכנסת בזמן אמת.
כאשר שיחת טלפון נכנסת מגיעה למספר ללא סוכן שהוקצה לו, או שסשן של וידג'ט אינטרנט מתחיל במפתח ניתן לפרסום במצב
mode="webhook", ThunderPhone שולח בקשה חוסמת
telephony.incoming / web.incoming אל
כתובת ה-webhook הישנה שלכם
וממתין לתגובת תצורה: עד 13 שניות עבור
שיחת טלפון, כולל ניסיון חוזר אחד, ועד 10 שניות עבור סשן וידג'ט
(ראו זמני קציבה וניסיונות חוזרים). השתמשו
בחילוף זה כדי לבחור באופן דינמי הנחיה, קול וכלים לכל שיחה —
ראו את המדריך לתצורת שיחות דינמית
לתבנית המלאה מקצה לקצה.
פסקי זמן וניסיונות חוזרים
ענו במהירות: המתקשר ממתין על הקו בזמן שאתם מחליטים.
לשיחת טלפון (telephony.incoming):
- הניסיון הראשון עשוי להימשך עד 10 שניות.
- אם מתרחש פסק זמן, לא ניתן להתחבר, או שמתקבלת תגובת
5xx,408או429, ThunderPhone מנסה פעם נוספת. הניסיון החוזר מקבל את הזמן שנותר מתוך תקציב של 13 שניות לכל ההחלפה: כ-3 שניות לאחר ניסיון ראשון שפסק זמנו, וכמעט כל 13 השניות לאחר כשל מהיר. לא מתבצע ניסיון חוזר כאשר נותרו פחות מ-2 שניות. - כל תגובת
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 | לא | כללו את התורות האחרונות של שמע המתקשר במקום רק את התור העדכני ביותר, כדי לשפר תיקונים ואיסוף נתונים עתירי איות או מספרים, בתוספת קטנה של השהיה ועלות. ברירת המחדל מופעלת עבור סשנים נכנסים ומושבתת עבור שיחות טלפון יוצאות; 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 | מחרוזת | null | לא | ask_first (ברירת מחדל) או follow_caller; null זהה ל-ask_first. אופן הטיפול של סוכן רב-לשוני באדם השני המדבר בשפה אחרת מבין שפותיו. ask_first עובר מיד כאשר הם מבקשים את השפה או אומרים בה משפט מלא, ושואל אם להמשיך בה לאחר אמירה קצרה, כגון "כן". follow_caller עובר לכל שפה מוגדרת שהם מדברים בה, אפילו מילה אחת. חל רק כאשר additional_languages מוגדר |
background_track | מחרוזת | null | לא | מזהה שמע סביבתי או null |
acknowledgement_prompt_mode | מחרוזת | לא | auto (ברירת מחדל) או manual (מוצרי Storm עם ack) |
acknowledgement_prompt | מחרוזת | לא | בשימוש כאשר acknowledgement_prompt_mode="manual" |
silence_interval_seconds | מספר שלם | null | לא | 5–120. שניות של שקט מצד המתקשר לפני בדיקת מצב |
silence_max_checkins | מספר שלם | null | לא | 1–10 |
silence_checkins_enabled | בוליאני | לא | ברירת המחדל היא true |
connect_tone_enabled | בוליאני | לא | ברירת המחדל היא false |
pronunciation_dicts | בוליאני | null | לא | ברירת המחדל היא false; null זהה ל-false. כאשר הערך הוא true, הדיבור של Cartesia בשיחה משתמש במילון ההגייה של ThunderPhone עבור שפת השיחה, כאשר קיים כזה. פועל עבור שיחות טלפון ושיחות בווידג'ט. חל רק על קולות Cartesia של ThunderPhone, ולא על קולות במפתח API משלכם של Cartesia. אינו בשימוש עם agent_id, שבו חלות הגדרות הסוכן השמור |
voicemail_action | מחרוזת | לא | prompt (ברירת מחדל), hangup או message |
voicemail_message | מחרוזת | לא | בשימוש כאשר voicemail_action="message" |
agent_name | מחרוזת | לא | שם תצוגה שמדווח ללוחות המחוונים ולווידג'ט |
org_name | מחרוזת | לא | שם תצוגה של הארגון עבור הדמות של הסוכן |
tools | מערך | לא | סכמות מוטמעות של כלי פונקציה (ראו כלי פונקציה) |
transfer_destinations | מערך של אובייקטים | null | לא | עד 50 רשומות {name, number} שאליהן שיחה זו יכולה להעביר, המאומתות כמו מספרי ההעברה של סוכן. הסוכן מעביר לפי שם ורק אליהן; [] פירושו שאין העברות. השמיטו שדה זה או שלחו null כדי לאפשר לסוכן להעביר למספר שהוא בוחר |
timezone | מחרוזת | לא | אזור זמן של IANA עבור שיחה זו, כגון America/Chicago. הסוכן מציין את התאריך והשעה בו, לפני אזור הזמן של הסוכן עצמו. מותר גם לצד agent_id. ערכים שאינם אזורי IANA מתעלמים מהם. ראו הגדרת אזור הזמן לכל שיחה |
call_id | מספר שלם | לא | הד אופציונלי של מזהה השיחה בבקשה; מתעלמים ממנו |
מכיוון ש-prompt ו-voice נדרשים, החזרת {} או כל
תגובה שנכשלת באימות דוחה את השיחה: שיחת טלפון מסתיימת
כמתואר ב-זמני קצוב וניסיונות חוזרים, ובקשת הפעלת
ווידג'ט נכשלת עם 422. בנתיב זה אין חלופה של סוכן סטטי
(למספר או למפתח במצב וובהוק אין סוכן משויך).
מגבלת גודל תגובה
מטפל לדוגמה
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 {}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({});
},
);תגובה עם כלי פונקציה
צרפו כלים כדי שה-AI יוכל לקרוא לממשקי ה-API שלכם במהלך השיחה:
{
"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 | גבוהה יותר | עמוקה | מענה ביניים אוטומטי בזמן החשיבה |