ThunderPhone 2.0 kini resmi hadir.Gunakan langsung secara mandiri, mulai 2¢/menit.Baca pengumumannya

Webhooks

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 yang memblokir telephony.incoming / web.incoming ke URL webhook lama Anda dan menunggu respons konfigurasi: hingga 13 detik untuk panggilan telepon, termasuk satu percobaan ulang, dan hingga 10 detik untuk sesi widget (lihat Batas waktu dan percobaan ulang). Gunakan pertukaran ini untuk memilih Prompt, suara, dan alat secara dinamis per panggilan — lihat panduan konfigurasi panggilan dinamis untuk pola menyeluruh.

Batas waktu dan percobaan ulang

Jawablah dengan cepat: penelepon menunggu di saluran saat Anda memutuskan.

Untuk panggilan telepon (telephony.incoming):

  • Upaya pertama dapat memakan waktu hingga 10 detik.
  • Jika mengalami batas waktu habis, tidak dapat terhubung, atau menerima respons 5xx, 408, atau 429, ThunderPhone mencoba sekali lagi. Percobaan ulang mendapatkan sisa dari batas waktu 13 detik untuk seluruh pertukaran: sekitar 3 detik setelah upaya pertama yang mengalami batas waktu habis, hampir seluruh 13 detik setelah kegagalan cepat. Tidak ada percobaan ulang jika sisa waktunya kurang dari 2 detik.
  • Respons 4xx lainnya, respons yang melebihi batas ukuran, atau 2xx yang isi responsnya bukan konfigurasi valid bersifat final dan tidak dicoba ulang.
  • Kedua upaya mengirim isi respons yang sama, header X-ThunderPhone-Signature yang sama, dan header X-ThunderPhone-Idempotency-Key yang sama (telephony-incoming-<call_id>), sehingga handler yang memiliki efek samping dapat mengenali percobaan ulang.

Tidak ada agen cadangan. Jika tidak ada konfigurasi yang dapat digunakan, panggilan langsung diakhiri alih-alih membiarkan penelepon berada di saluran hening. Panggilan tersebut muncul dalam riwayat panggilan sebagai panggilan gagal yang tidak ditagihkan dengan end_reason inbound_config_webhook_failed, dan peristiwa telephony.complete biasa dikirim.

Sesi widget (web.incoming) mendapatkan satu upaya hingga 10 detik. Jika gagal, permintaan sesi gagal dengan 502 atau 422.

Payload permintaan

Untuk panggilan telepon (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
KolomTipeDeskripsi
call_idintegerID panggilan — tetap sama di seluruh peristiwa untuk panggilan ini
from_numberstringNomor penelepon E.164
to_numberstringTujuan 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"
  }
}
BidangTipeDeskripsi
call_idintegerID panggilan
origin_domainstringAsal halaman yang menghosting widget, sebagaimana dikirim oleh peramban. Klien non-peramban dapat mengirim nilai apa pun, jadi jangan anggap ini sebagai bukti asal permintaan
publishable_key_prefixstringKarakter pertama dari kunci yang dapat dipublikasikan yang membuka sesi
language, primary_languagestringAda saat sesi widget meminta override bahasa
voicestringAda saat sesi widget meminta override suara
website_contextstringAda saat widget meneruskan konteks halaman per sesi

Bidang override (language, voice, website_context, dan variables peramban) hanya tiba saat kunci yang dapat dipublikasikan mengizinkannya di override peramban. Permintaan sesi yang menyertakan override yang tidak diizinkan oleh kunci tersebut ditolak sebelum webhook ini dipanggil.


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":   []
}
BidangJenisWajibDeskripsi
promptstringyaPrompt sistem yang mengarahkan agen
voicestringyaID suara dari GET /v1/voices, misalnya john. voice_name diterima sebagai alias. Suara yang tidak dikenal gagal validasi dan panggilan ditolak
productstringtidakDefaultnya spark. Diizinkan: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringtidakminimal, base (default), atau extra. Ditimpa untuk produk Storm: storm-extra* memaksa extra, storm-* lainnya memaksa base
audio_context_modestringtidakfull (default) atau reduced
watchdog_enabledbooleantidakAktifkan pengawasan untuk panggilan ini. Default false
additional_audio_contextboolean | nulltidakSertakan beberapa giliran terakhir audio penelepon, bukan hanya giliran terbaru, untuk meningkatkan koreksi serta pengumpulan data yang banyak berisi ejaan/angka dengan sedikit tambahan latensi/biaya. Default aktif untuk sesi masuk dan nonaktif untuk panggilan telepon keluar; null mempertahankan default
storm_feedback_modestringtidaknone, acknowledgement (default), atau tick. Juga menentukan apa yang didengar penelepon saat model audio memproses giliran ketika audio_tier adalah hearing
languagestringtidakSingkatan untuk primary_language
primary_languagestringtidakKode bahasa, dinormalisasi (default en). Kode yang tidak dapat diuraikan akan menolak panggilan
has_additional_languagesbooleantidakDefault false
additional_languagesarray of stringtidakBahasa tambahan yang dapat digunakan agen
native_voice_switchingbooleantidakDefault false. Saat panggilan beralih ke bahasa lain, ganti ke suara native untuk bahasa tersebut (disesuaikan berdasarkan gender), alih-alih mempertahankan suara yang dikonfigurasi
language_switchingstring | nulltidakask_first (default) atau follow_caller; null sama dengan ask_first. Menentukan cara agen multibahasa menangani lawan bicara yang menggunakan bahasa lain yang didukungnya. ask_first langsung beralih ketika mereka meminta bahasa tersebut atau mengucapkan kalimat lengkap dalam bahasa itu, dan menanyakan apakah akan melanjutkan dalam bahasa tersebut setelah ucapan singkat, seperti "ya". follow_caller beralih ke bahasa terkonfigurasi apa pun yang mereka gunakan, bahkan hanya satu kata. Hanya berlaku ketika additional_languages ditetapkan
audio_tierstring | nulltidakHanya product: "bolt". standard (default; "" dan null berarti sama) menjalankan tiga transkriptor (panggilan yang memproses PHI hanya menggunakan transkriptor yang tercakup BAA). lean menurunkan ke satu transkriptor, yang lebih sering salah dengar. hearing meningkatkan ke standard ditambah ansambel kuat model audio yang mendengarkan audio penelepon pada beberapa giliran, yang membutuhkan waktu sekitar 1–2 detik lebih lama. Produk lain mengabaikannya
endpointingstring | nulltidakfaster (200 md), fast (300 md), normal (500 md, default), atau patient (800 md); null sama dengan normal. Menentukan berapa lama agen Bolt v2 menunggu setelah penelepon berhenti sebelum meminta model memberikan respons. Waktu tunggu yang lebih singkat dapat menjawab lebih cepat, tetapi mengirim lebih banyak permintaan yang menjadi usang oleh kata-kata penelepon berikutnya, sehingga meningkatkan biaya model. Produk lain mengabaikannya
eagernessstring | nulltidakTidak digunakan lagi: nama endpointing hingga 19 Oktober 2026, tetapi masih dibaca. Diabaikan saat endpointing ditetapkan
intelligencestring | nulltidakexpress, lite, atau standard (default); null sama dengan standard. Tingkat kecerdasan agen bolt pada pipeline Bolt v2: menentukan model yang menulis responsnya. express memerlukan sekitar 600 md hingga kata pertama, lebih murah daripada Standard dan paling tidak andal dalam mengikuti instruksi; lite memerlukan sekitar 900 md, paling murah dan kurang andal; standard memerlukan sekitar 900 md dan terbaik dalam mengikuti instruksi. Respons Express dan Lite yang hanya akan memanggil tool diserahkan ke model Standard. Produk lain mengabaikannya
background_trackstring | nulltidakID audio ambient atau null
acknowledgement_prompt_modestringtidakauto (default) atau manual (produk Storm-with-ack)
acknowledgement_promptstringtidakDigunakan saat acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nulltidak5–120. Detik keheningan penelepon sebelum check-in
silence_max_checkinsinteger | nulltidak1–10
silence_checkins_enabledbooleantidakDefault true
silence_final_actionstringtidakSetelah check-in terakhir: hang_up (default) atau transfer ke silence_transfer_destination
silence_transfer_destinationstringtidakNama entri dalam transfer_destinations konfigurasi ini; wajib dengan transfer. Tanpa transfer_destinations, transfer tidak dapat dilakukan dan agen menutup panggilan
silence_final_messagestringtidak≤ 300 karakter, dibaca sebagaimana ditulis; "" (default) menggunakan baris default
connect_tone_enabledbooleantidakDefault false
pronunciation_dictsboolean | nulltidakDefault false; null sama dengan false. Saat true, suara Cartesia panggilan menggunakan kamus pelafalan ThunderPhone untuk bahasa panggilan, jika tersedia. Berfungsi untuk panggilan telepon dan widget. Hanya berlaku untuk suara Cartesia milik ThunderPhone sendiri, bukan suara pada kunci API Cartesia Anda sendiri. Tidak digunakan dengan agent_id, karena pengaturan agen yang tersimpan berlaku
voicemail_actionstringtidakprompt (default), hangup, atau message
voicemail_messagestringtidakDigunakan saat voicemail_action="message"
agent_namestringtidakNama tampilan yang dilaporkan ke dasbor dan widget
org_namestringtidakNama tampilan organisasi untuk persona agen
toolsarraytidakSkema function-tool inline (lihat Alat Fungsi)
transfer_destinationsarray of object | nulltidakHingga 50 entri {name, number} yang dapat dialihkan oleh panggilan ini, divalidasi seperti nomor transfer agen. Agen mengalihkan berdasarkan nama dan hanya ke entri tersebut; [] berarti tidak ada transfer. Hilangkan atau kirim null agar agen dapat mengalihkan ke nomor yang dipilihnya
timezonestringtidakZona waktu IANA untuk panggilan ini, seperti America/Chicago. Agen menyatakan tanggal dan waktu dalam zona waktu tersebut, bukan zona waktunya sendiri. Juga diizinkan di samping agent_id. Nilai yang bukan zona IANA diabaikan. Lihat Atur zona waktu per panggilan
call_idintegertidakGema opsional ID panggilan permintaan; diabaikan

Karena prompt dan voice wajib, mengembalikan {} atau respons apa pun yang gagal validasi akan menolak panggilan: panggilan telepon diakhiri seperti dijelaskan dalam Batas waktu dan percobaan ulang, dan permintaan sesi widget gagal 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

Python (FastAPI)
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 {}
Node.js (Express)
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

ProdukLatensiPenalaranKonfirmasi
sparkTerendahDasar—
boltRendahDitingkatkan—
storm-baseSedangKuat—
storm-base-with-ackSedangKuatPengisi otomatis saat berpikir
storm-extraLebih tinggiMendalam—
storm-extra-with-ackLebih tinggiMendalamPengisi otomatis saat berpikir

Terkait