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.complete / web.complete

Webhook không chặn được gửi khi cuộc gọi kết thúc, kèm theo bản chép lời, URL bản ghi và chỉ số.

Sự kiện hoàn tất được kích hoạt sau khi mỗi cuộc gọi kết thúc — cuộc gọi điện thoại đến, cuộc gọi điện thoại đi, cuộc gọi web hoặc cuộc gọi thử nghiệm (phiên mic trong trình tạo). Sự kiện này không chặn: phản hồi bằng bất kỳ mã 2xx nào.

Sự kiện được gửi qua cả hai đường dẫn:

Nội dung yêu cầu (lần gửi endpoint)

{
  "data": {
    "billable_minutes": 1.25,
    "billing_total_cents": 8,
    "call_id": 987654321,
    "direction": "inbound",
    "duration_seconds": 54,
    "end_reason": "user_hangup",
    "end_time": "2026-04-20T18:25:04.822Z",
    "extracted_data": {
      "status": "completed",
      "fields": {
        "customer_name": "Alex Morgan",
        "appointment_date": "2026-04-23"
      },
      "evidence": {
        "customer_name": {
          "quote": "My name is Alex Morgan",
          "speaker_role": "caller",
          "turn_index": 4
        },
        "appointment_date": {
          "quote": "April 23 works for me",
          "speaker_role": "caller",
          "turn_index": 7
        }
      },
      "verification": "verified",
      "field_reasons": {},
      "schema_version": "92850758e231a3c95a..."
    },
    "from_number": "+14155550199",
    "product": "spark",
    "recording_stopped_at": null,
    "recording_stopped_reason": null,
    "recording_url": "https://storage.example.com/…",
    "start_time": "2026-04-20T18:24:10.113Z",
    "status": "completed",
    "summary": "Alex Morgan called to book a follow-up visit and confirmed Thursday, April 23. The agent read the appointment back and Alex asked for a reminder text the day before.",
    "to_number": "+15551234567",
    "transcripts": [ /* see Transcript format */ ],
    "transfer_number": null,
    "unresolved_variables": ["campaign_owner"],
    "variables": {"campaign_name": "Spring renewals"},
    "voice": "john"
  },
  "event_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
  "type": "telephony.complete"
}
TrườngKiểuMô tả
call_idintegerỔn định trên mọi sự kiện của cuộc gọi này
agent_idinteger | nullTác nhân đã xử lý cuộc gọi, khi có tác nhân được chỉ định
agent_namestring | nullTác nhân đã xử lý cuộc gọi, khi có tác nhân được chỉ định
directionstringinbound, outbound, web, test. Payload lịch sử có thể chứa các giá trị cũ mic hoặc widget
from_number, to_numberstringE.164. from_number là giá trị nguyên văn "web" cho cuộc gọi web và cuộc gọi kiểm thử
origin_domainstringChỉ web/kiểm thử — nguồn gốc trang lưu trữ tiện ích (trống đối với phiên mic)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullĐược suy ra từ thời gian bắt đầu/kết thúc
statusstringcompleted hoặc failed
end_reasonstringXem bảng bên dưới
product, voicestringCấu hình tác nhân có hiệu lực tại thời điểm cuộc gọi
variablesobjectBiến đầu vào được chụp lại tại thời điểm cuộc gọi bắt đầu
unresolved_variablesarrayTên biến được tham chiếu bởi cấu hình cuộc gọi nhưng không được cung cấp khi cuộc gọi bắt đầu
transfer_numberstring | nullĐược đặt khi cuộc gọi được chuyển tiếp
recording_urlstring | nullURL đã ký có thời hạn; hãy tải xuống ngay. null khi không có tệp ghi âm
recording_stopped_attimestamp | nullThời điểm ghi âm bị dừng trước khi cuộc gọi kết thúc; nội dung sau thời điểm này không có trong bản ghi. null khi quá trình ghi âm kéo dài đến hết cuộc gọi hoặc cuộc gọi không được ghi âm
recording_stopped_reasonstring | nullLý do ghi âm dừng sớm. warm_transfer: ghi âm dừng ngay trước khi một lần chuyển cuộc gọi có giám sát gọi đến đích và vẫn tắt ngay cả khi việc chuyển tiếp thất bại và người gọi quay lại tác nhân. Coi các giá trị khác là thông tin tham khảo; có thể bổ sung thêm. null khi recording_stopped_at là null
billable_minutesnumberSố phút được tính phí, làm tròn đến một phần tư phút gần nhất (mỗi khoảng 15 giây, tối thiểu 0,25). Cuộc gọi chuyển thẳng đến hộp thư thoại vẫn báo cáo số phút đo thực tế tại đây, nhưng phí được giới hạn ở một phút theo mức phí của gói.
billing_total_centsintegerCent USD
transcriptsarrayMục bản chép lời theo từng lượt; có thể trống khi không có bản chép lời
summarystring | nullTóm tắt sau cuộc gọi bằng ngôn ngữ thông thường (hai đến bốn câu văn xuôi, tối đa 2.000 ký tự, được viết bằng ngôn ngữ của cuộc gọi: ngôn ngữ chính của tác nhân trừ khi cuộc gọi ghi đè ngôn ngữ đó) khi tác nhân đã bật Tạo tóm tắt cuộc gọi trong Dữ liệu cần thu thập ở trình tạo tác nhân (call_summary_enabled trên Tác nhân) và quá trình tạo thành công; nếu không thì là null, bao gồm các cuộc gọi quá ngắn để tóm tắt, cuộc gọi không có bản chép lời và tác nhân đã tắt ghi âm. Tóm tắt được tạo trong cùng bước sau cuộc gọi với quá trình trích xuất, vì vậy cài đặt Chờ dữ liệu trích xuất trước khi gửi webhook hoàn tất cuộc gọi (extraction_blocks_completion) quyết định liệu sự kiện này có chờ hay không: ở chế độ chờ, tóm tắt sẵn sàng trước khi sự kiện này được gửi (bản thân tóm tắt có tối đa 30 giây trong ngân sách 75 giây của bước); khi sự kiện không chờ (never hoặc chuyển tiếp theo except_transfers), trường này là null và tóm tắt không được gửi lại trong call.data_extracted, vì vậy hãy đọc sau từ summary trên đối tượng Cuộc gọi. Cũng có trên trang cuộc gọi trong bảng điều khiển
extracted_dataobject | nullKết quả trích xuất có cấu trúc gồm status, fields, evidence, verification, field_reasons và schema_version. Mỗi trường không phải null có trích dẫn chính xác đã được kiểm tra cấu trúc (tối đa 1.000 ký tự), cùng vai trò người nói và chỉ mục lượt; các trích dẫn dài hơn do mô hình trả về sẽ bị từ chối thay vì cắt ngắn. Bằng chứng là null khi trường là null. verified nghĩa là mọi ứng viên đã nhận đúng một kết luận độc lập hợp lệ. unavailable cũng bao gồm đầu ra của trình xác minh bị sai định dạng hoặc không đầy đủ; các kết luận một phần hợp lệ vẫn được áp dụng, trong khi ứng viên không có một kết luận hợp lệ sẽ được đặt thành null. status là completed, failed, exhausted, skipped hoặc skipped_recording_disabled; null khi tác nhân không có trường trích xuất
steps_tracearrayChỉ có trên cuộc gọi mà tác nhân có bước hoặc chuyển giao. Thứ tự cuộc gọi đi qua các bước: mỗi mục có t_ms (mili giây kể từ khi cuộc gọi bắt đầu), agent (khóa của tác nhân, a cộng với ID của tác nhân), step (ID bước hoặc null khi chuyển giao đến tác nhân không có bước) và via (start, model, condition, on_enter hoặc handoff). Chỉ chứa ID và thời gian nên vẫn được lưu giữ ngay cả khi ghi âm bị tắt
collected_valuesobjectChỉ có trên cuộc gọi mà tác nhân có bước hoặc chuyển giao. Các giá trị tác nhân thu thập trong các bước, được khóa theo tên trường thu thập, cùng với <tool>_result cho kết quả on_enter_tool của mỗi bước. Bị bỏ qua khi nội dung cuộc gọi không được lưu giữ (ghi âm bị tắt)

Lý do kết thúc

Giá trịÝ nghĩa
user_hangupBên từ xa ngắt cuộc gọi trước
ai_hangupTác nhân AI chủ động kết thúc cuộc gọi
ai_transferTác nhân AI đã chuyển cuộc gọi; transfer_number được thiết lập
ai_warm_transferTác nhân AI đã hoàn tất chuyển cuộc gọi có giám sát
voicemail_hangupĐã phát hiện thư thoại và cuộc gọi kết thúc theo voicemail_action của bạn
max_durationCuộc gọi đạt giới hạn thời lượng tối đa: giới hạn của tác nhân, của chính cuộc gọi hoặc giới hạn 6 giờ của nền tảng. Cuộc gọi do tác nhân AI hoặc người gọi kết thúc sau cảnh báo giới hạn thời gian vẫn giữ ai_hangup hoặc user_hangup
supersededPhiên đã được thay thế bằng một phiên mới hơn
inbound_config_webhook_failedCuộc gọi đến một số ở chế độ webhook: webhook cấu hình không trả về cấu hình có thể sử dụng, nên cuộc gọi đã kết thúc trước khi tác nhân trả lời. status là failed và cuộc gọi không bị tính phí
unknownKhông thể xác định lý do kết thúc

Định dạng transcript

Mỗi mục trong transcripts là một lượt hội thoại. Các vai trò là user (lời nói của người gọi), model (lời nói của tác nhân AI và lệnh gọi công cụ), tool (kết quả công cụ) và system (sự kiện cuộc gọi như chuyển đổi ngôn ngữ).

[
  {
    "role": "user",
    "content_type": "text/plain",
    "content": "Hi, I'm calling about my appointment.",
    "start_ms": 1200,
    "end_ms":   4100,
    "audio_url": "https://storage.example.com/…"
  },
  {
    "role": "model",
    "content_type": "text/plain",
    "content": "Sure, what date works best?",
    "start_ms": 4200,
    "end_ms":   6100
  },
  {
    "role": "model",
    "content_type": "application/json",
    "content": {
      "tool_call": "search_appointments",
      "arguments": { "date": "2026-04-21" }
    }
  },
  {
    "role": "tool",
    "content_type": "application/json",
    "content": {
      "tool_name": "search_appointments",
      "response": { "available_slots": ["9:00 AM", "2:00 PM"] }
    }
  }
]
TrườngLoạiMô tả
rolestringuser, model, tool hoặc system
content_typestringtext/plain cho lời nói; application/json cho lệnh gọi công cụ, kết quả công cụ và sự kiện hệ thống
contentstring | objectVăn bản lời nói hoặc đối tượng có cấu trúc hiển thị ở trên. Lệnh gọi công cụ: {"tool_call": name, "arguments": {…}}. Kết quả công cụ: {"tool_name": name, "response": {…}}
start_ms, end_msintegerĐộ lệch tính từ lúc cuộc gọi bắt đầu, tính bằng mili giây. Có mặt khi biết thời điểm âm thanh
ttfa_msintegerThời gian đến âm thanh đầu tiên cho lượt model, khi được đo
audio_url, audio_urlsstring / arrayURL đã ký có thời hạn cho âm thanh của lượt, khi âm thanh được ghi theo từng lượt

Để xem lịch sử lượt đầy đủ có cấu trúc (bao gồm dấu ngắt, prompt xác nhận và vị trí thô), hãy dùng GET /v1/calls/{call_id}/history.

Khác biệt của payload cũ

Envelope webhook cũ dùng một URL duy nhất là {"type": "telephony.complete" | "web.complete", "data": {…}} và không có event_id, đồng thời data của nó khác với payload endpoint:

Payload hoàn tất cũ cũng chứa agent_id và agent_name.

  • Mảng lượt nằm trong history, không phải transcripts (cùng schema lượt như ở trên).
  • Tập trường là báo cáo thô khi cuộc gọi kết thúc và có thể bao gồm các trường nội bộ bổ sung ngoài bảng trên — hãy coi các trường không xác định là thông tin tham khảo.
  • Cuộc gọi web (direction: "web") bỏ qua from_number / to_number và thêm origin_domain.
  • Cuộc gọi kiểm tra mic trong Builder được báo cáo là telephony.complete trên đường dẫn cũ (hệ thống endpoint ánh xạ chúng thành web.complete).
  • Điều phối chuyển cuộc gọi: khi một cuộc gọi kết thúc bằng chuyển cuộc gọi, webhook cũ được gọi đồng bộ và có thể trả về {"transfer_ready": false} để báo hiệu mục tiêu nhận chuyển cuộc gọi chưa sẵn sàng. Mọi phản hồi khác (hoặc không có webhook cũ) sẽ cho phép chuyển cuộc gọi tiếp tục. Các lần gửi endpoint không bao giờ được tham chiếu cho việc này.

Trình xử lý mẫu

Python (FastAPI)
import hashlib
import hmac
import json
import os
 
from fastapi import FastAPI, HTTPException, Request
 
app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
 
def verify(body: bytes, signature: str) -> bool:
    expected = hmac.new(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"] in ("telephony.complete", "web.complete"):
        data = event["data"]
        # Endpoint deliveries use "transcripts"; the legacy webhook uses "history".
        turns = data.get("transcripts") or data.get("history") or []
        await persist_call_record(
            call_id=data["call_id"],
            turns=turns,
            recording_url=data.get("recording_url"),
        )
        if data["end_reason"] in ("ai_transfer", "ai_warm_transfer"):
            await notify_team(data.get("transfer_number"), data["call_id"])
    return {"ok": True}
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" }),
  async (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
    if (["telephony.complete", "web.complete"].includes(event.type)) {
      const data = event.data;
      // Endpoint deliveries use "transcripts"; the legacy webhook uses "history".
      const turns = data.transcripts ?? data.history ?? [];
      await persistCallRecord({ ...data, turns });
      if (["ai_transfer", "ai_warm_transfer"].includes(data.end_reason)) {
        await notifyTeam(data.transfer_number, data.call_id);
      }
    }
    res.json({ ok: true });
  },
);

Trường hợp sử dụng phổ biến

Tích hợp CRM

Lưu transcript và URL bản ghi của từng cuộc gọi cùng với hồ sơ khách hàng của bạn.

Phân tích

Truyền transcript đến pipeline để lập mô hình chủ đề, trích xuất tín hiệu CSAT hoặc theo dõi tỷ lệ chuyển máy.

Rà soát chất lượng

Mở cuộc gọi trong công cụ QA để con người rà soát hoặc chạy qua mô hình đánh giá của riêng bạn.

Thông báo

Kích hoạt thông báo cho thành viên trong nhóm khi chuyển máy / thất bại.


Liên quan