telephony.incoming / web.incoming
Webhook חוסם שמעצב את תצורת השיחה הנכנסת בזמן אמת.
כאשר שיחת טלפון נכנסת מגיעה למספר ללא סוכן
משויך, או שסשן של וידג'ט אינטרנט מתחיל במפתח ניתן לפרסום במצב
mode="webhook", ThunderPhone שולחת בקשת
telephony.incoming / web.incoming חוסמת אל
כתובת הוובהוק הישנה שלכם
וממתינה עד 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 | string | כן | הנחיית מערכת שמנחה את הסוכן |
voice | string | כן | מזהה קול מתוך GET /v1/voices, לדוגמה john. voice_name מתקבל כשם חלופי. קולות לא מוכרים נכשלים באימות ודוחים את השיחה |
product | string | לא | ברירת המחדל היא spark. הערכים המותרים: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | string | לא | minimal, base (ברירת מחדל), או extra. מוחלף עבור מוצרי Storm: storm-extra* כופה extra, ומוצרי storm-* אחרים כופים base |
audio_context_mode | string | לא | full (ברירת מחדל) או reduced |
watchdog_enabled | boolean | לא | הפעלת פיקוח עבור שיחה זו. ברירת המחדל היא false |
additional_audio_context | boolean | null | לא | הכללת הסבבים האחרונים של אודיו המתקשר במקום הסבב האחרון בלבד, לשיפור תיקונים ואיסוף נתונים עתיר איות ומספרים, בתוספת קטנה של שיהוי ועלות. מופעל כברירת מחדל עבור סשנים נכנסים ומושבת עבור שיחות טלפון יוצאות; null שומר על ברירת המחדל |
storm_feedback_mode | string | לא | none, acknowledgement (ברירת מחדל), או tick |
language | string | לא | קיצור עבור primary_language |
primary_language | string | לא | קוד שפה, מנורמל (ברירת המחדל en). קודים שלא ניתן לפתור דוחים את השיחה |
has_additional_languages | boolean | לא | ברירת המחדל היא false |
additional_languages | array of string | לא | שפות נוספות שהסוכן יכול לעבור אליהן |
native_voice_switching | boolean | לא | ברירת המחדל היא false. כאשר השיחה עוברת לשפה אחרת, החליפו לקול שמקורו בשפה זו (מותאם לפי מגדר) במקום לשמור על הקול שהוגדר |
background_track | string | null | לא | מזהה אודיו סביבתי או null |
acknowledgement_prompt_mode | string | לא | auto (ברירת מחדל) או manual (מוצרי Storm-with-ack) |
acknowledgement_prompt | string | לא | משמש כאשר acknowledgement_prompt_mode="manual" |
silence_interval_seconds | integer | null | לא | 5–120. שניות של שקט מצד המתקשר לפני בדיקה |
silence_max_checkins | integer | null | לא | 1–10 |
silence_checkins_enabled | boolean | לא | ברירת המחדל היא true |
connect_tone_enabled | boolean | לא | ברירת המחדל היא false |
voicemail_action | string | לא | prompt (ברירת מחדל), hangup, או message |
voicemail_message | string | לא | משמש כאשר voicemail_action="message" |
agent_name | string | לא | שם תצוגה המדווח ללוחות הבקרה ולווידג'ט |
org_name | string | לא | שם התצוגה של הארגון עבור דמות הסוכן |
tools | array | לא | סכמות של כלי פונקציה מוטבעים (ראו כלי פונקציה) |
call_id | integer | לא | הד אופציונלי של מזהה השיחה בבקשה; מתעלמים ממנו |
מכיוון ש-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 | גבוהה יותר | עמוקה | מענה ביניים אוטומטי בזמן החשיבה |