ThunderPhone 2.0 এখন লাইভ।নিজেই ব্যবহার শুরু করুন, 2¢/মিনিট থেকে।ঘোষণাটি পড়ুন

Webhooks

telephony.complete / web.complete

ট্রান্সক্রিপ্ট, রেকর্ডিং URL এবং মেট্রিকসহ কল শেষ হলে নন-ব্লকিং webhook পাঠানো হয়।

প্রতিটি কল শেষ হওয়ার পরে একটি completion ইভেন্ট ট্রিগার হয় — ইনবাউন্ড টেলিফোনি, আউটবাউন্ড টেলিফোনি, ওয়েব কল বা টেস্ট কল (বিল্ডার মাইক সেশন)। এটি non-blocking: যেকোনো 2xx দিয়ে প্রতিক্রিয়া দিন।

ইভেন্টটি উভয় পাথে পাঠানো হয়:

রিকোয়েস্ট পেলোড (এন্ডপয়েন্ট ডেলিভারি)

{
  "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_url": "https://storage.example.com/…",
    "start_time": "2026-04-20T18:24:10.113Z",
    "status": "completed",
    "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"
}
ফিল্ডধরনবিবরণ
call_idintegerএই কলের প্রতিটি ইভেন্টে স্থিতিশীল থাকে
agent_idinteger | nullকোনো এজেন্ট বরাদ্দ থাকলে, কলটি পরিচালনাকারী এজেন্ট
agent_namestring | nullকোনো এজেন্ট বরাদ্দ থাকলে, কলটি পরিচালনাকারী এজেন্ট
directionstringinbound, outbound, web, test। ঐতিহাসিক পেলোডে পুরোনো mic বা widget মান থাকতে পারে
from_number, to_numberstringE.164। ওয়েব কল ও টেস্ট কলের জন্য from_number আক্ষরিক "web" হয়
origin_domainstringশুধু ওয়েব/টেস্টের জন্য — উইজেট হোস্ট করা পৃষ্ঠার উৎস (মাইক সেশনের জন্য খালি)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullশুরু/শেষ থেকে নির্ধারিত
statusstringcompleted অথবা failed
end_reasonstringনিচের টেবিল দেখুন
product, voicestringকলের সময় কার্যকর এজেন্ট কনফিগারেশন
variablesobjectকল শুরু হওয়ার সময় স্ন্যাপশট নেওয়া ইনপুট ভেরিয়েবল
unresolved_variablesarrayকল কনফিগে উল্লেখ করা, কিন্তু কল শুরুর সময় সরবরাহ না করা ভেরিয়েবলের নাম
transfer_numberstring | nullকল ট্রান্সফার হলে সেট করা হয়
recording_urlstring | nullমেয়াদ-সীমিত সাইন করা URL; দ্রুত ডাউনলোড করুন। কোনো রেকর্ডিং আর্টিফ্যাক্ট উপলভ্য না থাকলে null
billable_minutesnumberবিল করা মিনিট, নিকটতম এক-চতুর্থাংশ মিনিটে রাউন্ড করা হয় (15-সেকেন্ডের বৃদ্ধি, সর্বনিম্ন 0.25)। সরাসরি ভয়েসমেইলে যাওয়া কলও এখানে তাদের প্রকৃত মিটার করা মিনিট রিপোর্ট করে, তবে চার্জ প্ল্যান রেটে এক মিনিটে সীমাবদ্ধ থাকে।
billing_total_centsintegerUSD সেন্ট
transcriptsarrayপ্রতিটি টার্নের ট্রান্সক্রিপ্ট এন্ট্রি; ট্রান্সক্রিপ্ট অনুপলভ্য হলে খালি হতে পারে
extracted_dataobject | nullstatus, fields, evidence, verification, field_reasons, এবং schema_version-সহ কাঠামোবদ্ধ এক্সট্রাকশন ফলাফল। প্রতিটি নন-নাল ফিল্ডে সঠিকভাবে কাঠামোগত যাচাই করা উদ্ধৃতি থাকে (সর্বোচ্চ 1,000 অক্ষর), সঙ্গে তার বক্তার ভূমিকা ও টার্ন ইনডেক্স; মডেল-ফেরত দীর্ঘ উদ্ধৃতি ছোট করে দেওয়ার পরিবর্তে প্রত্যাখ্যান করা হয়। ফিল্ড null হলে প্রমাণ null হয়। verified অর্থ প্রতিটি প্রার্থী ঠিক একটি বৈধ স্বাধীন রায় পেয়েছে। unavailable বিকৃত বা আংশিক ভেরিফায়ার আউটপুটও অন্তর্ভুক্ত করে; বৈধ আংশিক রায় প্রযোজ্য থাকে, আর একটি বৈধ রায়বিহীন প্রার্থীকে নাল করা হয়। status হলো completed, failed, exhausted, skipped, অথবা skipped_recording_disabled; এজেন্টের কোনো এক্সট্রাকশন ফিল্ড না থাকলে null

শেষের কারণ

মানঅর্থ
user_hangupদূরবর্তী পক্ষ আগে কল কেটেছে
ai_hangupAI ইচ্ছাকৃতভাবে কল শেষ করেছে
ai_transferAI কল ট্রান্সফার করেছে; transfer_number সেট করা আছে
ai_warm_transferAI একটি ওয়ার্ম (অ্যাটেন্ডেড) ট্রান্সফার সম্পন্ন করেছে
voicemail_hangupভয়েসমেইল শনাক্ত হয়েছে এবং আপনার voicemail_action অনুযায়ী কল শেষ হয়েছে
max_durationকল সর্বোচ্চ সময়সীমায় পৌঁছেছে
supersededসেশনটি একটি নতুন সেশন দ্বারা প্রতিস্থাপিত হয়েছে
unknownশেষের কারণ নির্ধারণ করা যায়নি

ট্রান্সক্রিপ্ট ফরম্যাট

transcripts-এর প্রতিটি এন্ট্রি একটি কথোপকথনের পালা। রোলগুলো হলো user (কলারের কথা), model (এজেন্টের কথা এবং টুল কল), tool (টুলের ফলাফল), এবং system (ভাষা পরিবর্তনের মতো কল ইভেন্ট)।

[
  {
    "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"] }
    }
  }
]
ফিল্ডধরনবর্ণনা
roleস্ট্রিংuser, model, tool, অথবা system
content_typeস্ট্রিংকথার জন্য text/plain; টুল কল, টুল ফলাফল এবং সিস্টেম ইভেন্টের জন্য application/json
contentস্ট্রিং | অবজেক্টকথার টেক্সট, অথবা উপরে দেখানো স্ট্রাকচার্ড অবজেক্ট। টুল কল: {"tool_call": name, "arguments": {…}}। টুল ফলাফল: {"tool_name": name, "response": {…}}
start_ms, end_msইন্টিজারকল শুরুর পর থেকে অফসেট, ms। অডিওর টাইমিং জানা থাকলে উপস্থিত থাকে
ttfa_msইন্টিজারপরিমাপ করা হলে, একটি model পালার প্রথম অডিও পর্যন্ত সময়
audio_url, audio_urlsস্ট্রিং / অ্যারেপ্রতি-পালায় রেকর্ড করা হলে, সেই পালার অডিওর মেয়াদোত্তীর্ণ স্বাক্ষরিত URL

বাধাদান চিহ্ন, স্বীকৃতি prompt এবং কাঁচা অবস্থানসহ সম্পূর্ণ স্ট্রাকচার্ড পালার ইতিহাসের জন্য, GET /v1/calls/{call_id}/history ব্যবহার করুন।

লিগ্যাসি পেলোডের পার্থক্য

লিগ্যাসি একক-URL webhook এনভেলপটি হলো {"type": "telephony.complete" | "web.complete", "data": {…}}, যেখানে কোনো event_id নেই, এবং এর data এন্ডপয়েন্ট পেলোড থেকে ভিন্ন:

লিগ্যাসি কমপ্লিশন পেলোডে agent_id এবং agent_name-ও থাকে।

  • পালার অ্যারে transcripts-এর পরিবর্তে history-এর অধীনে থাকে (উপরের একই পালা স্কিমা)।
  • ফিল্ড সেটটি কাঁচা কল-শেষ রিপোর্ট এবং এতে উপরের টেবিলের বাইরেও অতিরিক্ত অভ্যন্তরীণ ফিল্ড থাকতে পারে — অজানা ফিল্ডগুলোকে তথ্যগত হিসেবে বিবেচনা করুন।
  • ওয়েব কলগুলো (direction: "web") from_number / to_number বাদ দেয় এবং origin_domain যোগ করে।
  • Builder মাইক টেস্ট কলগুলো লিগ্যাসি পাথে telephony.complete হিসেবে রিপোর্ট হয় (এন্ডপয়েন্ট সিস্টেম সেগুলোকে web.complete-এ ম্যাপ করে)।
  • ট্রান্সফার সমন্বয়: কোনো কল ট্রান্সফারে শেষ হলে, লিগ্যাসি webhook সিঙ্ক্রোনাসভাবে কল করা হয় এবং হস্তান্তরের লক্ষ্য প্রস্তুত নয় বোঝাতে {"transfer_ready": false} ফেরত দিতে পারে। অন্য যেকোনো প্রতিক্রিয়া (অথবা কোনো লিগ্যাসি webhook না থাকলে) ট্রান্সফার এগোতে দেয়। এন্ডপয়েন্ট ডেলিভারি কখনোই এ জন্য বিবেচিত হয় না।

উদাহরণ হ্যান্ডলার

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

সাধারণ ব্যবহারের ক্ষেত্র

CRM ইন্টিগ্রেশন

আপনার গ্রাহক রেকর্ডের সঙ্গে প্রতিটি কলের ট্রান্সক্রিপ্ট ও রেকর্ডিং URL সংরক্ষণ করুন।

অ্যানালিটিক্স

বিষয় মডেলিং, CSAT সিগন্যাল এক্সট্রাকশন বা ট্রান্সফার-রেট পর্যবেক্ষণের জন্য ট্রান্সক্রিপ্ট একটি পাইপলাইনে স্ট্রিম করুন।

গুণমান পর্যালোচনা

মানব পর্যালোচনার জন্য QA টুলে কল খুলুন অথবা আপনার নিজস্ব মূল্যায়ন মডেলের মাধ্যমে সেগুলো চালান।

বিজ্ঞপ্তি

ট্রান্সফার বা ব্যর্থতার ক্ষেত্রে একজন মানব টিম সদস্যকে ট্রিগার করুন।


সম্পর্কিত