ThunderPhone 2.0 đã chính thức ra mắt.Tự đăng ký và sử dụng, chỉ từ 2¢/phút.Xem thông báo ra mắt

Webhooks

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 một 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 có thể công khai ở mode="webhook", ThunderPhone gửi một yêu cầu chặn telephony.incoming / web.incoming đến URL webhook cũ của bạn và chờ phản hồi cấu hình: tối đa 13 giây cho một cuộc gọi điện thoại, bao gồm một lần thử lại, và tối đa 10 giây cho một phiên widget (xem Thời gian chờ và thử lại). Dùng trao đổi này để chọn động 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 toàn diện.

Hết thời gian chờ và thử lại

Phản hồi nhanh: người gọi đang chờ trên đường dây trong khi bạn quyết định.

Đối với cuộc gọi điện thoại (telephony.incoming):

  • Lần thử đầu tiên có thể mất tối đa 10 giây.
  • Nếu hết thời gian chờ, không thể kết nối hoặc nhận được phản hồi 5xx, 408 hay 429, ThunderPhone sẽ thử lại một lần nữa. Lần thử lại nhận phần còn lại của ngân sách 13 giây cho toàn bộ quá trình trao đổi: khoảng 3 giây sau lần thử đầu tiên hết thời gian chờ, hoặc gần như toàn bộ 13 giây sau một lỗi xảy ra nhanh. Sẽ không thử lại nếu còn chưa đến 2 giây.
  • Bất kỳ phản hồi 4xx nào khác, phản hồi vượt quá giới hạn kích thước, hoặc phản hồi 2xx có phần thân không phải là cấu hình hợp lệ đều là cuối cùng và không được thử lại.
  • Cả hai lần thử đều gửi cùng phần thân, cùng tiêu đề X-ThunderPhone-Signature, và cùng tiêu đề X-ThunderPhone-Idempotency-Key (telephony-incoming-<call_id>), vì vậy trình xử lý có tác dụng phụ có thể nhận biết lần thử lại.

Không có tác nhân dự phòng. Nếu không nhận được cấu hình có thể sử dụng, cuộc gọi sẽ được kết thúc ngay thay vì để người gọi chờ trên đường dây im lặng. Cuộc gọi xuất hiện trong lịch sử cuộc gọi là cuộc gọi thất bại, không tính phí với end_reason là inbound_config_webhook_failed, và sự kiện telephony.complete thông thường sẽ được gửi.

Một phiên widget (web.incoming) chỉ có một lần thử tối đa 10 giây. Nếu thất bại, yêu cầu phiên sẽ thất bại với 502 hoặc 422.

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ườngKiểuMô tả
call_idintegerID cuộc gọi — không đổi trên mọi sự kiện của cuộc gọi này
from_numberstringSố điện thoại người gọi theo E.164
to_numberstringĐí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ườngKiểuMô tả
call_idintegerID cuộc gọi
origin_domainstringNguồn gốc của trang lưu trữ widget, theo thông tin trình duyệt gửi. Ứng dụng không phải trình duyệt có thể gửi bất kỳ giá trị nào, vì vậy không xem đây là bằng chứng về nơi yêu cầu xuất phát
publishable_key_prefixstringCác ký tự đầu của publishable key đã mở phiên
language, primary_languagestringCó mặt khi phiên widget yêu cầu ghi đè ngôn ngữ
voicestringCó mặt khi phiên widget yêu cầu ghi đè giọng nói
website_contextstringCó mặt khi widget truyền ngữ cảnh trang theo từng phiên

Các trường ghi đè (language, voice, website_context và mọi variables của trình duyệt) chỉ được gửi khi publishable key cho phép chúng trong các ghi đè của trình duyệt. Yêu cầu phiên chứa một trường mà key không cho phép sẽ bị từ chối trước khi webhook này được gọi.


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ườngLoạiBắt buộcMô tả
promptstringcóSystem prompt điều khiển tác nhân AI
voicestringcóID 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 vượt qua bước xác thực và bị từ chối cuộc gọi
productstringkhôngMặc định là spark. Giá trị được phép: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringkhôngminimal, base (mặc định) hoặc extra. Bị ghi đè cho sản phẩm Storm: storm-extra* buộc dùng extra, các storm-* khác buộc dùng base
audio_context_modestringkhôngfull (mặc định) hoặc reduced
watchdog_enabledbooleankhôngBật giám sát cho cuộc gọi này. Mặc định là false
additional_audio_contextboolean | nullkhôngBao 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, cải thiện việc sửa lỗi và thu thập dữ liệu có nhiều chữ cái/số, với mức tăng nhỏ về độ trễ và chi phí. Mặc định bật cho phiên gọi đến và tắt cho cuộc gọi điện thoại gọi đi; null giữ nguyên giá trị mặc định
storm_feedback_modestringkhôngnone, acknowledgement (mặc định) hoặc tick
languagestringkhôngViết tắt của primary_language
primary_languagestringkhôngMã ngôn ngữ, được chuẩn hóa (mặc định là en). Mã không thể phân giải sẽ bị từ chối cuộc gọi
has_additional_languagesbooleankhôngMặc định là false
additional_languagesarray of stringkhôngCác ngôn ngữ bổ sung mà tác nhân AI có thể chuyển sang
native_voice_switchingbooleankhôngMặ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
language_switchingstring | nullkhôngask_first (mặc định) hoặc follow_caller; null giống với ask_first. Cách tác nhân AI đa ngôn ngữ xử lý khi người kia nói một ngôn ngữ khác mà tác nhân AI hỗ trợ. ask_first chuyển ngay khi họ yêu cầu ngôn ngữ đó hoặc nói một câu hoàn chỉnh bằng ngôn ngữ đó, và hỏi có muốn tiếp tục bằng ngôn ngữ đó không sau một câu ngắn, như “có”. follow_caller chuyển sang bất kỳ ngôn ngữ nào đã cấu hình mà họ nói, kể cả chỉ một từ. Chỉ áp dụng khi đặt additional_languages
background_trackstring | nullkhôngID âm thanh nền hoặc null
acknowledgement_prompt_modestringkhôngauto (mặc định) hoặc manual (sản phẩm Storm-with-ack)
acknowledgement_promptstringkhôngĐược dùng khi acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullkhông5–120. Số giây người gọi im lặng trước khi kiểm tra lại
silence_max_checkinsinteger | nullkhông1–10
silence_checkins_enabledbooleankhôngMặc định là true
connect_tone_enabledbooleankhôngMặc định là false
pronunciation_dictsboolean | nullkhôngMặc định là false; null giống với false. Khi là true, giọng nói Cartesia của cuộc gọi sử dụng từ điển phát âm của ThunderPhone cho ngôn ngữ của cuộc gọi, nếu có. Hoạt động cho cuộc gọi điện thoại và widget. Chỉ áp dụng cho giọng nói Cartesia riêng của ThunderPhone, không áp dụng cho giọng nói trên khóa API Cartesia của bạn. Không dùng với agent_id, nơi áp dụng các cài đặt của tác nhân AI đã lưu
voicemail_actionstringkhôngprompt (mặc định), hangup hoặc message
voicemail_messagestringkhôngĐược dùng khi voicemail_action="message"
agent_namestringkhôngTên hiển thị được báo cáo trên dashboard và widget
org_namestringkhôngTên hiển thị của tổ chức cho hình tượng của tác nhân AI
toolsarraykhôngSchema công cụ hàm nội tuyến (xem Công cụ hàm)
transfer_destinationsarray of object | nullkhôngTối đa 50 mục nhập {name, number} mà cuộc gọi này có thể chuyển đến, được xác thực như số chuyển cuộc gọi của tác nhân AI. Tác nhân AI chuyển theo tên và chỉ đến các mục này; [] nghĩa là không chuyển cuộc gọi. Bỏ qua hoặc gửi null để cho phép tác nhân AI chuyển đến số mà nó chọn
timezonestringkhôngMúi giờ IANA cho cuộc gọi này, chẳng hạn America/Chicago. Tác nhân AI nêu ngày và giờ theo múi giờ này, ưu tiên hơn múi giờ riêng của tác nhân AI. Cũng được phép đặt cùng agent_id. Các giá trị không phải múi giờ IANA sẽ bị bỏ qua. Xem Đặt múi giờ cho từng cuộc gọi
call_idintegerkhôngPhản hồi tùy chọn của ID 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 vượt qua bước xác thực sẽ từ chối cuộc gọi: cuộc gọi điện thoại sẽ kết thúc như mô tả trong Hết thời gian chờ và thử lại, và yêu cầu phiên widget sẽ thất bại với mã 422. Không có phương án dự phòng bằng tác nhân tĩnh trên luồng này (một số điện thoại hoặc khóa ở chế độ webhook không có tác nhân được gán).


Giới hạn kích thước phản hồi


Ví dụ về trình xử lý

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({});
  },
);

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ậnXác nhận
sparkThấp nhấtCơ bản—
boltThấpCải thiện—
storm-baseTrung bìnhMạnh—
storm-base-with-ackTrung bìnhMạnhTự động chèn lời đệm khi đang suy nghĩ
storm-extraCao hơnChuyên sâu—
storm-extra-with-ackCao hơnChuyên sâuTự động chèn lời đệm khi đang suy nghĩ

Liên quan