ThunderPhone 2.0 अब लाइव है।सेल्फ़-सर्व, 2¢/मिनट से।घोषणा पढ़ें

Webhooks

telephony.complete / web.complete

कॉल समाप्त होने पर ट्रांसक्रिप्ट, रिकॉर्डिंग URL और मेट्रिक्स के साथ डिलीवर किया जाने वाला नॉन-ब्लॉकिंग वेबहुक।

हर कॉल समाप्त होने के बाद एक कंप्लीशन इवेंट ट्रिगर होता है — इनबाउंड टेलीफोनी, आउटबाउंड टेलीफोनी, वेब कॉल या टेस्ट कॉल (बिल्डर माइक सेशन)। यह नॉन-ब्लॉकिंग है: किसी भी 2xx के साथ रिस्पॉन्ड करें।

इवेंट दोनों पाथ पर डिलीवर होता है:

  • Webhook एंडपॉइंट्स को telephony.complete (फोन कॉल) या web.complete (वेब कॉल और बिल्डर माइक टेस्ट कॉल) मिलता है, जिसमें नीचे डॉक्यूमेंट किया गया स्थिर पेलोड, हर डिलीवरी के लिए एक event_id, 30 s का टाइमआउट और 24 h तक रिट्राई शामिल हैं।
  • लीगेसी सिंगल-URL webhook को थोड़ा अलग पेलोड के साथ एक सिंक्रोनस प्रयास मिलता है (10 s टाइमआउट, कोई रिट्राई नहीं) — देखें लीगेसी पेलोड अंतर।

अनुरोध पेलोड (एंडपॉइंट डिलीवरी)

{
  "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"
}
फ़ील्डटाइपविवरण
call_idintegerइस कॉल के हर इवेंट में स्थिर रहता है
agent_idinteger | nullकॉल संभालने वाला एजेंट, जब कोई असाइन किया गया हो
agent_namestring | nullकॉल संभालने वाला एजेंट, जब कोई असाइन किया गया हो
directionstringinbound, outbound, web, test। ऐतिहासिक पेलोड में लेगसी mic या widget वैल्यू हो सकती हैं
from_number, to_numberstringE.164। वेब कॉल और टेस्ट कॉल के लिए from_number का शाब्दिक मान "web" होता है
origin_domainstringकेवल वेब/टेस्ट के लिए — वह पेज ऑरिजिन जिस पर विजेट होस्ट किया गया था (mic सेशन के लिए खाली)
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
recording_stopped_attimestamp | nullजब कॉल समाप्त होने से पहले रिकॉर्डिंग रोकी गई; इस समय के बाद की कोई भी सामग्री रिकॉर्डिंग में नहीं होती। रिकॉर्डिंग कॉल के अंत तक चलने या कॉल रिकॉर्ड न होने पर null
recording_stopped_reasonstring | nullरिकॉर्डिंग जल्दी क्यों रुकी। warm_transfer: यह वॉर्म ट्रांसफ़र द्वारा डेस्टिनेशन को कॉल करने से ठीक पहले रुकती है और ट्रांसफ़र विफल होने तथा कॉलर के एजेंट के पास लौटने पर भी बंद रहती है। अन्य वैल्यू को जानकारी के रूप में मानें; और जोड़ी जा सकती हैं। recording_stopped_at के null होने पर null
billable_minutesnumberबिल किए गए मिनट, निकटतम चौथाई मिनट तक राउंड किए गए (15-सेकंड की वृद्धि, न्यूनतम 0.25)। सीधे वॉइसमेल पर जाने वाली कॉल भी यहां अपने वास्तविक मीटर किए गए मिनट रिपोर्ट करती हैं, लेकिन शुल्क प्लान रेट पर एक मिनट तक सीमित रहता है।
billing_total_centsintegerUSD सेंट
transcriptsarrayप्रति-टर्न ट्रांसक्रिप्ट एंट्री; ट्रांसक्रिप्ट उपलब्ध न होने पर खाली हो सकती हैं
summarystring | nullसादे भाषा में कॉल के बाद का सारांश (गद्य के दो से चार वाक्य, अधिकतम 2,000 कैरेक्टर, कॉल की भाषा में लिखा गया: एजेंट की प्राथमिक भाषा, जब तक कॉल ने उसे ओवरराइड न किया हो), जब एजेंट बिल्डर में एकत्र करने के लिए डेटा के अंतर्गत एजेंट के लिए कॉल सारांश जनरेट करें चालू हो (call_summary_enabled एजेंट पर) और जनरेशन सफल हो; अन्यथा null, जिसमें सारांश के लिए बहुत छोटी कॉल, बिना ट्रांसक्रिप्ट वाली कॉल और रिकॉर्डिंग बंद वाले एजेंट शामिल हैं। सारांश एक्सट्रैक्शन के समान पोस्ट-कॉल स्टेप में जनरेट होता है, इसलिए कॉल-पूर्ण वेबहुक भेजने से पहले एक्सट्रैक्ट किए गए डेटा की प्रतीक्षा करें सेटिंग (extraction_blocks_completion) तय करती है कि यह इवेंट इसकी प्रतीक्षा करता है या नहीं: प्रतीक्षा मोड में यह इवेंट भेजे जाने से पहले सारांश तैयार होता है (सारांश को स्टेप के 75-सेकंड बजट में अधिकतम 30 सेकंड मिलते हैं); जब इवेंट प्रतीक्षा नहीं करता (never, या except_transfers के अंतर्गत ट्रांसफ़र), तो यह फ़ील्ड null होती है और सारांश call.data_extracted में दोबारा नहीं भेजा जाता, इसलिए इसे बाद में कॉल ऑब्जेक्ट के summary से पढ़ें। डैशबोर्ड में कॉल पेज पर भी उपलब्ध
extracted_dataobject | 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
steps_tracearrayकेवल उन कॉल पर जिनके एजेंट के पास स्टेप्स या हैंडऑफ़्स हों। कॉल इनके माध्यम से जिस क्रम में चली: हर एंट्री में t_ms (कॉल शुरू होने के बाद के मिलीसेकंड), agent (एजेंट की कुंजी, उसकी ID के साथ a), step (स्टेप ID, या बिना स्टेप्स वाले एजेंट को हैंडऑफ़ पर null) और via (start, model, condition, on_enter या handoff) होते हैं। इसमें केवल ID और समय होते हैं, इसलिए रिकॉर्डिंग बंद होने पर भी इसे रखा जाता है
collected_valuesobjectकेवल उन कॉल पर जिनके एजेंट के पास स्टेप्स या हैंडऑफ़्स हों। एजेंट ने अपने स्टेप्स में जो वैल्यू एकत्र कीं, वे कलेक्ट फ़ील्ड नाम से कुंजीबद्ध होती हैं, साथ ही प्रत्येक स्टेप के on_enter_tool परिणाम के लिए <tool>_result होता है। कॉल की सामग्री संरक्षित न होने पर छोड़ा जाता है (रिकॉर्डिंग बंद)

समाप्ति के कारण

मानअर्थ
user_hangupरिमोट पार्टी ने पहले कॉल काट दिया
ai_hangupAI ने जानबूझकर कॉल समाप्त किया
ai_transferAI ने कॉल ट्रांसफ़र किया; transfer_number सेट है
ai_warm_transferAI ने वॉर्म (अटेंडेड) ट्रांसफ़र पूरा किया
voicemail_hangupवॉइसमेल का पता चला और आपके voicemail_action के अनुसार कॉल समाप्त किया गया
max_durationकॉल अधिकतम अवधि सीमा तक पहुंच गई: एजेंट की या कॉल की अपनी सीमा, या 6 घंटे की प्लेटफ़ॉर्म सीमा। समय सीमा चेतावनी के बाद AI या कॉलर द्वारा समाप्त की गई कॉल में ai_hangup या user_hangup बना रहता है
supersededसेशन को एक नए सेशन से बदल दिया गया
inbound_config_webhook_failedवेबहुक मोड में किसी नंबर पर इनबाउंड कॉल: कॉन्फ़िगरेशन वेबहुक ने उपयोग योग्य कॉन्फ़िगरेशन नहीं लौटाया, इसलिए एजेंट के जवाब देने से पहले कॉल समाप्त कर दिया गया। status failed है और कॉल का बिल नहीं किया जाता
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स्ट्रिंग / ऐरेप्रति-टर्न रिकॉर्ड होने पर टर्न के ऑडियो के लिए समाप्त होने वाले साइन किए गए URLs

पूरी तरह संरचित टर्न हिस्ट्री के लिए (इंटरप्शन मार्कर, ऐक-प्रॉम्प्ट और रॉ पोज़िशन के साथ), उपयोग करें 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 टूल में खोलें, या उन्हें अपने इवैल्यूएशन मॉडल से चलाएं।

नोटिफिकेशन

ट्रांसफर / फेल्योर पर किसी मानवीय टीममेट को ट्रिगर करें।


संबंधित