telephony.incoming / web.incoming
Webhook pemblokiran yang membentuk konfigurasi panggilan masuk secara real time.
Saat panggilan telepon masuk mencapai nomor tanpa agen yang
ditetapkan, atau sesi widget web dimulai pada kunci yang dapat
dipublikasikan dalam mode="webhook", ThunderPhone mengirimkan
permintaan telephony.incoming / web.incoming yang memblokir ke
URL webhook lama
Anda dan menunggu hingga 10 detik untuk respons konfigurasi.
Gunakan pertukaran ini untuk memilih Prompt, suara, dan alat secara
dinamis untuk setiap panggilan — lihat panduan konfigurasi panggilan dinamis
untuk pola lengkap dari awal hingga akhir.
Pertukaran yang memblokir tidak memiliki fallback: jika handler Anda mengembalikan
status non-2xx, mengalami waktu habis, atau mengembalikan konfigurasi yang gagal
validasi, panggilan ditolak (panggilan telepon tidak tersambung; permintaan sesi
widget gagal dengan 502/422). Jawab dengan cepat — penelepon mendengar nada
dering saat Anda menentukan pilihan.
Payload permintaan
Untuk panggilan telepon (telephony.incoming):
{
"type": "telephony.incoming",
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}| Kolom | Tipe | Deskripsi |
|---|---|---|
call_id | integer | ID panggilan — tetap sama di seluruh peristiwa untuk panggilan ini |
from_number | string | Nomor penelepon E.164 |
to_number | string | Tujuan E.164 (salah satu nomor ThunderPhone Anda) |
Untuk sesi widget web (web.incoming), data mengidentifikasi
halaman yang menyematkan widget, bukan nomor telepon:
{
"type": "web.incoming",
"data": {
"call_id": 987654322,
"origin_domain": "https://example.com",
"publishable_key_prefix": "pk_live_a1b2"
}
}| Kolom | Tipe | Deskripsi |
|---|---|---|
call_id | integer | ID panggilan |
origin_domain | string | Asal halaman yang menghosting widget |
publishable_key_prefix | string | Karakter pertama dari kunci yang dapat dipublikasikan yang membuka sesi |
language, primary_language | string | Ada saat sesi widget meminta penggantian bahasa |
voice | string | Ada saat sesi widget meminta penggantian suara |
website_context | string | Ada saat widget meneruskan konteks halaman per sesi |
Skema respons
Kembalikan objek JSON yang menjelaskan konfigurasi agen untuk panggilan ini.
prompt dan voice wajib diisi; semua yang lain bersifat opsional.
{
"prompt": "You are a helpful booking assistant for Acme Restaurant.",
"voice": "john",
"product": "spark",
"background_track": null,
"tools": []
}| Kolom | Tipe | Wajib | Deskripsi |
|---|---|---|---|
prompt | string | ya | System Prompt yang mengarahkan agen |
voice | string | ya | ID suara dari GET /v1/voices, misalnya john. voice_name diterima sebagai alias. Suara yang tidak dikenal gagal divalidasi dan panggilan ditolak |
product | string | tidak | Default-nya adalah spark. Yang diizinkan: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | string | tidak | minimal, base (default), atau extra. Ditimpa untuk produk Storm: storm-extra* memaksa extra, produk storm-* lainnya memaksa base |
audio_context_mode | string | tidak | full (default) atau reduced |
watchdog_enabled | boolean | tidak | Aktifkan pengawasan untuk panggilan ini. Default false |
additional_audio_context | boolean | null | tidak | Sertakan beberapa giliran terakhir audio penelepon, bukan hanya giliran terbaru, untuk meningkatkan koreksi serta pengumpulan data yang banyak memuat ejaan/angka dengan sedikit tambahan latensi/biaya. Aktif secara default untuk sesi masuk dan nonaktif untuk panggilan telepon keluar; null mempertahankan default |
storm_feedback_mode | string | tidak | none, acknowledgement (default), atau tick |
language | string | tidak | Singkatan untuk primary_language |
primary_language | string | tidak | Kode bahasa, dinormalisasi (default en). Kode yang tidak dapat diuraikan menolak panggilan |
has_additional_languages | boolean | tidak | Default false |
additional_languages | array of string | tidak | Bahasa tambahan yang dapat digunakan agen |
native_voice_switching | boolean | tidak | Default false. Saat panggilan beralih ke bahasa lain, ganti ke suara yang merupakan penutur asli bahasa tersebut (disesuaikan berdasarkan gender), alih-alih mempertahankan suara yang dikonfigurasi |
background_track | string | null | tidak | ID audio ambient atau null |
acknowledgement_prompt_mode | string | tidak | auto (default) atau manual (produk Storm-with-ack) |
acknowledgement_prompt | string | tidak | Digunakan saat acknowledgement_prompt_mode="manual" |
silence_interval_seconds | integer | null | tidak | 5–120. Detik keheningan penelepon sebelum pemeriksaan |
silence_max_checkins | integer | null | tidak | 1–10 |
silence_checkins_enabled | boolean | tidak | Default true |
connect_tone_enabled | boolean | tidak | Default false |
voicemail_action | string | tidak | prompt (default), hangup, atau message |
voicemail_message | string | tidak | Digunakan saat voicemail_action="message" |
agent_name | string | tidak | Nama tampilan yang dilaporkan ke dasbor dan widget |
org_name | string | tidak | Nama tampilan organisasi untuk persona agen |
tools | array | tidak | Skema alat fungsi inline (lihat Function Tools) |
call_id | integer | tidak | Echo opsional dari ID panggilan pada permintaan; diabaikan |
Karena prompt dan voice wajib diisi, mengembalikan {} atau respons apa pun
yang gagal divalidasi akan menolak panggilan dengan 422 — tidak ada fallback agen
statis pada jalur ini (nomor atau kunci dalam mode webhook tidak memiliki agen yang
ditetapkan).
Batas ukuran respons
Contoh handler
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({});
},
);Respons dengan function tool
Tambahkan tool agar AI dapat memanggil API Anda di tengah percakapan:
{
"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"
}
}
}
]
}Lembar ringkas tingkatan produk
| Produk | Latensi | Penalaran | Konfirmasi |
|---|---|---|---|
spark | Terendah | Dasar | — |
bolt | Rendah | Ditingkatkan | — |
storm-base | Sedang | Kuat | — |
storm-base-with-ack | Sedang | Kuat | Pengisi otomatis saat berpikir |
storm-extra | Lebih tinggi | Mendalam | — |
storm-extra-with-ack | Lebih tinggi | Mendalam | Pengisi otomatis saat berpikir |
Terkait
Peristiwa akhir panggilan yang tidak memblokir.
Skema JSON lengkap untuk tools[] dan kontrak endpoint yang ditandatangani.
Berlangganan beberapa URL ke telephony.incoming / web.incoming.
Pola untuk Prompt, alat, dan pengujian A/B per penelepon.