ThunderPhone 2.0 आता लाइव्ह आहे.स्वतःच सुरू करा—2¢/मिनिटपासून.घोषणा वाचा

Webhooks

telephony.complete / web.complete

कॉल संपल्यावर ट्रान्स्क्रिप्ट, रेकॉर्डिंग URL आणि मेट्रिक्ससह वितरित होणारा नॉन-ब्लॉकिंग webhook.

प्रत्येक कॉल संपल्यानंतर पूर्णता इव्हेंट ट्रिगर होतो — इनबाउंड टेलिफोनी, आउटबाउंड टेलिफोनी, वेब कॉल किंवा चाचणी कॉल (बिल्डर माइक सत्र). तो नॉन-ब्लॉकिंग आहे: कोणत्याही 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_idपूर्णांकया कॉलसाठीच्या प्रत्येक इव्हेंटमध्ये स्थिर
agent_idपूर्णांक | nullनियुक्त केला असल्यास, कॉल हाताळणारा एजंट
agent_nameस्ट्रिंग | nullनियुक्त केला असल्यास, कॉल हाताळणाऱ्या एजंटचे नाव
directionस्ट्रिंगinbound, outbound, web, test. ऐतिहासिक पेलोडमध्ये जुनी mic किंवा widget मूल्ये असू शकतात
from_number, to_numberस्ट्रिंगE.164. वेब कॉल आणि टेस्ट कॉलसाठी from_number हे अक्षरशः "web" असते
origin_domainस्ट्रिंगफक्त वेब/टेस्टसाठी — विजेट होस्ट करणाऱ्या पेजचा ओरिजिन (माइक सेशनसाठी रिक्त)
start_time, end_timeटाइमस्टॅम्पISO 8601 UTC
duration_secondsपूर्णांक | nullसुरूवात/समाप्तीपासून व्युत्पन्न
statusस्ट्रिंगcompleted किंवा failed
end_reasonस्ट्रिंगखालील तक्ता पहा
product, voiceस्ट्रिंगकॉलच्या वेळी लागू असलेले एजंट कॉन्फिगरेशन
variablesऑब्जेक्टकॉल सुरू होताना स्नॅपशॉट केलेले इनपुट व्हेरिएबल्स
unresolved_variablesअॅरेकॉल कॉन्फिगने संदर्भित केलेली, परंतु कॉल सुरू होताना पुरवली नसलेली व्हेरिएबल नावे
transfer_numberस्ट्रिंग | nullकॉल ट्रान्सफर झाल्यावर सेट केले जाते
recording_urlस्ट्रिंग | nullकालबाह्य होणारा स्वाक्षरीत URL; त्वरित डाउनलोड करा. रेकॉर्डिंग आर्टिफॅक्ट उपलब्ध नसल्यास null
billable_minutesसंख्याबिल केलेली मिनिटे, जवळच्या पाव मिनिटापर्यंत पूर्णांकित (15-सेकंद वाढी, किमान 0.25). थेट व्हॉइसमेलवर जाणारे कॉलही येथे त्यांची प्रत्यक्ष मीटर केलेली मिनिटे नोंदवतात, परंतु आकारणी प्लॅन दरानुसार एका मिनिटापर्यंत मर्यादित असते.
billing_total_centsपूर्णांकUSD सेंट
transcriptsअॅरेप्रत्येक टर्नसाठी ट्रान्सक्रिप्ट नोंदी; ट्रान्सक्रिप्ट उपलब्ध नसल्यास रिक्त असू शकते
extracted_dataऑब्जेक्ट | nullstatus, fields, evidence, verification, field_reasons, आणि schema_version असलेला संरचित एक्स्ट्रॅक्शन परिणाम. प्रत्येक non-null फील्डमध्ये अचूक संरचनात्मकरीत्या तपासलेला उद्धरण मजकूर (जास्तीत जास्त 1,000 वर्ण), तसेच त्याची स्पीकर भूमिका आणि टर्न इंडेक्स असतो; मॉडेलने परत केलेले जास्त लांबीचे उद्धरण कापण्याऐवजी नाकारले जातात. फील्ड null असल्यास एव्हिडन्स null असते. verified म्हणजे प्रत्येक उमेदवाराला नेमका एक वैध स्वतंत्र निर्णय मिळाला. unavailable मध्ये चुकीचा स्वरूपाचा किंवा अंशतः मिळालेला व्हेरिफायर आउटपुटही समाविष्ट असतो; वैध अंशतः निर्णय तरीही लागू होतात, तर एकही वैध निर्णय नसलेले उमेदवार null केले जातात. 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इंटीजरकॉल सुरू झाल्यापासूनचे ऑफसेट, मिलीसेकंदमध्ये. ऑडिओची वेळ ज्ञात असल्यास उपस्थित
ttfa_msइंटीजरमोजल्यास, model फेरीसाठी पहिल्या ऑडिओपर्यंतचा वेळ
audio_url, audio_urlsस्ट्रिंग / अॅरेप्रत्येक फेरीसाठी रेकॉर्ड केल्यास, त्या फेरीच्या ऑडिओसाठी कालबाह्य होणारे साइन केलेले URL

व्यत्यय चिन्हे, अॅक्नॉलेजमेंट prompt आणि मूळ स्थानांसह पूर्णपणे संरचित फेरी इतिहासासाठी, GET /v1/calls/{call_id}/history वापरा.

जुन्या पेलोडमधील फरक

जुन्या सिंगल-URL वेबहुक एन्व्हलपचे स्वरूप {"type": "telephony.complete" | "web.complete", "data": {…}} असे असून त्यात event_id नसते, आणि त्यातील data एंडपॉइंट पेलोडपेक्षा वेगळे असते:

जुन्या पूर्णता पेलोडमध्ये agent_id आणि agent_name देखील असतात.

  • फेरी अॅरे transcripts ऐवजी history अंतर्गत असतो (वरच्या प्रमाणेच फेरी स्कीमा).
  • फील्ड संच हा कॉल संपल्यावरचा मूळ अहवाल असतो आणि त्यात वरील तक्त्यापलीकडील अतिरिक्त अंतर्गत फील्ड असू शकतात — अज्ञात फील्डना केवळ माहितीपर माना.
  • वेब कॉल (direction: "web") मध्ये from_number / to_number वगळलेले असतात आणि origin_domain जोडलेले असते.
  • बिल्डर मायक्रोफोन चाचणी कॉल जुन्या मार्गावर telephony.complete म्हणून नोंदवले जातात (एंडपॉइंट सिस्टम त्यांना web.complete वर मॅप करते).
  • ट्रान्सफर समन्वय: कॉल ट्रान्सफरमध्ये संपल्यावर, जुना वेबहुक समकालिकपणे कॉल केला जातो आणि हस्तांतरणाचे लक्ष्य तयार नसल्याचे सूचित करण्यासाठी तो {"transfer_ready": false} असे उत्तर देऊ शकतो. इतर कोणतेही उत्तर (किंवा जुना वेबहुक नसल्यास) ट्रान्सफर पुढे जाऊ देते. यासाठी एंडपॉइंट वितरणांचा कधीही सल्ला घेतला जात नाही.

हँडलरचे उदाहरण

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 साधनामध्ये कॉल उघडा किंवा त्यांना तुमच्या स्वतःच्या मूल्यमापन मॉडेलमधून चालवा.

सूचना

ट्रान्सफर / अयशस्वीतेवर मानवी टीम सदस्याला सूचना द्या.


संबंधित