telephony.incoming / web.incoming
Webhook chặn định hình cấu hình cuộc gọi đến theo thời gian thực.
Khi một cuộc gọi điện thoại đến số chưa được gán tác nhân AI, hoặc một phiên widget web bắt đầu trên khóa publishable ở mode="webhook", ThunderPhone gửi một yêu cầu telephony.incoming / web.incoming chặn đến URL webhook cũ của bạn và chờ tối đa 10 giây để nhận phản hồi cấu hình. Dùng quy trình này để chọn linh hoạt prompt, giọng nói và công cụ cho từng cuộc gọi — xem hướng dẫn cấu hình cuộc gọi động để biết quy trình đầy đủ.
Quy trình trao đổi chặn không có phương án dự phòng: nếu handler của bạn trả về trạng thái không phải 2xx, hết thời gian chờ hoặc trả về cấu hình không vượt qua xác thực, cuộc gọi sẽ bị từ chối (cuộc gọi điện thoại không kết nối; yêu cầu phiên widget thất bại với 502/422). Phản hồi nhanh — người gọi đang nghe âm báo đổ chuông trong khi bạn quyết định.
Payload yêu cầu
Đối với cuộc gọi điện thoại (telephony.incoming):
{
"type": "telephony.incoming",
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}| Trường | Kiểu | Mô tả |
|---|---|---|
call_id | integer | ID cuộc gọi — không đổi trên mọi sự kiện của cuộc gọi này |
from_number | string | Số điện thoại người gọi theo E.164 |
to_number | string | Đích đến theo E.164 (một trong các số ThunderPhone của bạn) |
Đối với phiên widget web (web.incoming), data xác định trang nhúng thay vì số điện thoại:
{
"type": "web.incoming",
"data": {
"call_id": 987654322,
"origin_domain": "https://example.com",
"publishable_key_prefix": "pk_live_a1b2"
}
}| Trường | Kiểu | Mô tả |
|---|---|---|
call_id | integer | ID cuộc gọi |
origin_domain | string | Origin của trang lưu trữ widget |
publishable_key_prefix | string | Các ký tự đầu tiên của khóa publishable đã mở phiên |
language, primary_language | string | Có mặt khi phiên widget yêu cầu ghi đè ngôn ngữ |
voice | string | Có mặt khi phiên widget yêu cầu ghi đè giọng nói |
website_context | string | Có mặt khi widget truyền ngữ cảnh trang cho từng phiên |
Lược đồ phản hồi
Trả về một đối tượng JSON mô tả cấu hình tác nhân AI cho cuộc gọi này.
prompt và voice là bắt buộc; mọi trường khác là tùy chọn.
{
"prompt": "You are a helpful booking assistant for Acme Restaurant.",
"voice": "john",
"product": "spark",
"background_track": null,
"tools": []
}| Trường | Loại | Bắt buộc | Mô tả |
|---|---|---|---|
prompt | string | có | System prompt điều khiển tác nhân AI |
voice | string | có | Mã giọng nói từ GET /v1/voices, ví dụ: john. voice_name được chấp nhận làm bí danh. Giọng nói không xác định sẽ không qua xác thực và cuộc gọi bị từ chối |
product | string | không | Mặc định là spark. Giá trị được phép: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | string | không | minimal, base (mặc định), hoặc extra. Bị ghi đè đối với sản phẩm Storm: storm-extra* buộc dùng extra, các storm-* khác buộc dùng base |
audio_context_mode | string | không | full (mặc định) hoặc reduced |
watchdog_enabled | boolean | không | Bật giám sát cho cuộc gọi này. Mặc định là false |
additional_audio_context | boolean | null | không | Bao gồm vài lượt âm thanh gần nhất của người gọi thay vì chỉ lượt gần nhất, giúp cải thiện việc chỉnh sửa và thu thập dữ liệu nhiều đánh vần/số với mức tăng nhỏ về độ trễ/chi phí. Mặc định bật cho phiên cuộc gọi đến và tắt cho cuộc gọi điện thoại đi; null giữ nguyên mặc định |
storm_feedback_mode | string | không | none, acknowledgement (mặc định), hoặc tick |
language | string | không | Cú pháp rút gọn cho primary_language |
primary_language | string | không | Mã ngôn ngữ, được chuẩn hóa (mặc định en). Mã không thể phân giải sẽ từ chối cuộc gọi |
has_additional_languages | boolean | không | Mặc định là false |
additional_languages | array of string | không | Ngôn ngữ bổ sung mà tác nhân AI có thể chuyển sang |
native_voice_switching | boolean | không | Mặc định là false. Khi cuộc gọi chuyển sang ngôn ngữ khác, đổi sang giọng nói bản ngữ của ngôn ngữ đó (khớp theo giới tính) thay vì giữ giọng nói đã cấu hình |
background_track | string | null | không | Mã âm thanh nền hoặc null |
acknowledgement_prompt_mode | string | không | auto (mặc định) hoặc manual (sản phẩm Storm-with-ack) |
acknowledgement_prompt | string | không | Được dùng khi acknowledgement_prompt_mode="manual" |
silence_interval_seconds | integer | null | không | 5–120. Số giây người gọi im lặng trước khi kiểm tra |
silence_max_checkins | integer | null | không | 1–10 |
silence_checkins_enabled | boolean | không | Mặc định là true |
connect_tone_enabled | boolean | không | Mặc định là false |
voicemail_action | string | không | prompt (mặc định), hangup, hoặc message |
voicemail_message | string | không | Được dùng khi voicemail_action="message" |
agent_name | string | không | Tên hiển thị được báo cáo cho bảng điều khiển và widget |
org_name | string | không | Tên hiển thị của tổ chức cho vai trò của tác nhân AI |
tools | array | không | Lược đồ công cụ hàm nội tuyến (xem Công cụ hàm) |
call_id | integer | không | Giá trị phản chiếu tùy chọn của mã cuộc gọi trong yêu cầu; bị bỏ qua |
Vì prompt và voice là bắt buộc, việc trả về {} hoặc bất kỳ
phản hồi nào không qua xác thực sẽ từ chối cuộc gọi với 422 — không có
phương án dự phòng tác nhân AI tĩnh trên đường dẫn này (một số hoặc khóa ở chế độ webhook
không có tác nhân AI được gán).
Giới hạn kích thước phản hồi
Ví dụ về trình xử lý
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({});
},
);Phản hồi kèm công cụ hàm
Đính kèm công cụ để tác nhân AI có thể gọi API của bạn trong khi trò chuyện:
{
"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"
}
}
}
]
}Bảng tham chiếu nhanh về các gói sản phẩm
| Sản phẩm | Độ trễ | Lập luận | Xác nhận |
|---|---|---|---|
spark | Thấp nhất | Cơ bản | — |
bolt | Thấp | Cải thiện | — |
storm-base | Trung bình | Mạnh | — |
storm-base-with-ack | Trung bình | Mạnh | Tự động chèn lời đệm khi đang suy nghĩ |
storm-extra | Cao hơn | Chuyên sâu | — |
storm-extra-with-ack | Cao hơn | Chuyên sâu | Tự động chèn lời đệm khi đang suy nghĩ |