---
title: "telephony.complete / web.complete"
description: "कॉल समाप्त होने पर ट्रांसक्रिप्ट, रिकॉर्डिंग URL और मेट्रिक्स के साथ डिलीवर किया जाने वाला नॉन-ब्लॉकिंग वेबहुक।"
---

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

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

* **[Webhook एंडपॉइंट्स](/hi/webhooks/endpoints)** को
  `telephony.complete` (फोन कॉल) या `web.complete` (वेब कॉल और
  बिल्डर माइक टेस्ट कॉल) मिलता है, जिसमें नीचे डॉक्यूमेंट किया गया स्थिर पेलोड,
  हर डिलीवरी के लिए एक `event_id`, 30 s का टाइमआउट और
  [24 h तक रिट्राई](/hi/webhooks/overview) शामिल हैं।
* **[लीगेसी सिंगल-URL webhook](/api-reference/organizations#legacy-single-url-webhook)**
  को थोड़ा अलग पेलोड के साथ एक सिंक्रोनस प्रयास मिलता है (10 s टाइमआउट, कोई रिट्राई नहीं) — देखें
  [लीगेसी पेलोड अंतर](#legacy-payload-differences)।

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

```json
{
  "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_id` | integer | इस कॉल के हर इवेंट में स्थिर रहता है |
| `agent_id` | integer \| null | कॉल संभालने वाला एजेंट, जब कोई असाइन किया गया हो |
| `agent_name` | string \| null | कॉल संभालने वाला एजेंट, जब कोई असाइन किया गया हो |
| `direction` | string | `inbound`, `outbound`, `web`, `test`। ऐतिहासिक पेलोड में लेगसी `mic` या `widget` वैल्यू हो सकती हैं |
| `from_number`, `to_number` | string | E.164। वेब कॉल और टेस्ट कॉल के लिए `from_number` का शाब्दिक मान `"web"` होता है |
| `origin_domain` | string | **केवल वेब/टेस्ट के लिए** — वह पेज ऑरिजिन जिस पर विजेट होस्ट किया गया था (mic सेशन के लिए खाली) |
| `start_time`, `end_time` | timestamp | ISO 8601 UTC |
| `duration_seconds` | integer \| null | शुरू/अंत समय से प्राप्त |
| `status` | string | `completed` या `failed` |
| `end_reason` | string | नीचे दी गई टेबल देखें |
| `product`, `voice` | string | कॉल के समय प्रभावी एजेंट कॉन्फ़िगरेशन |
| `variables` | object | कॉल शुरू होने पर स्नैपशॉट किए गए इनपुट वेरिएबल |
| `unresolved_variables` | array | कॉल कॉन्फ़िगरेशन द्वारा संदर्भित, लेकिन कॉल शुरू होने पर उपलब्ध नहीं कराए गए वेरिएबल नाम |
| `transfer_number` | string \| null | कॉल ट्रांसफ़र होने पर सेट होता है |
| `recording_url` | string \| null | समाप्त होने वाला साइन किया हुआ URL; तुरंत डाउनलोड करें। कोई रिकॉर्डिंग आर्टिफैक्ट उपलब्ध न होने पर `null` |
| `recording_stopped_at` | timestamp \| null | जब कॉल समाप्त होने से पहले रिकॉर्डिंग रोकी गई; इस समय के बाद की कोई भी सामग्री रिकॉर्डिंग में नहीं होती। रिकॉर्डिंग कॉल के अंत तक चलने या कॉल रिकॉर्ड न होने पर `null` |
| `recording_stopped_reason` | string \| null | रिकॉर्डिंग जल्दी क्यों रुकी। `warm_transfer`: यह [वॉर्म ट्रांसफ़र](/hi/guides/call-transfers) द्वारा डेस्टिनेशन को कॉल करने से ठीक पहले रुकती है और ट्रांसफ़र विफल होने तथा कॉलर के एजेंट के पास लौटने पर भी बंद रहती है। अन्य वैल्यू को जानकारी के रूप में मानें; और जोड़ी जा सकती हैं। `recording_stopped_at` के `null` होने पर `null` |
| `billable_minutes` | number | बिल किए गए मिनट, निकटतम चौथाई मिनट तक राउंड किए गए (15-सेकंड की वृद्धि, न्यूनतम 0.25)। सीधे वॉइसमेल पर जाने वाली कॉल भी यहां अपने वास्तविक मीटर किए गए मिनट रिपोर्ट करती हैं, लेकिन शुल्क प्लान रेट पर एक मिनट तक सीमित रहता है। |
| `billing_total_cents` | integer | USD सेंट |
| `transcripts` | array | प्रति-टर्न ट्रांसक्रिप्ट एंट्री; ट्रांसक्रिप्ट उपलब्ध न होने पर खाली हो सकती हैं |
| `summary` | string \| null | सादे भाषा में कॉल के बाद का सारांश (गद्य के दो से चार वाक्य, अधिकतम 2,000 कैरेक्टर, कॉल की भाषा में लिखा गया: एजेंट की प्राथमिक भाषा, जब तक कॉल ने उसे ओवरराइड न किया हो), जब एजेंट बिल्डर में **एकत्र करने के लिए डेटा** के अंतर्गत एजेंट के लिए **कॉल सारांश जनरेट करें** चालू हो (`call_summary_enabled` [एजेंट](/api-reference/agents) पर) और जनरेशन सफल हो; अन्यथा `null`, जिसमें सारांश के लिए बहुत छोटी कॉल, बिना ट्रांसक्रिप्ट वाली कॉल और रिकॉर्डिंग बंद वाले एजेंट शामिल हैं। सारांश एक्सट्रैक्शन के समान पोस्ट-कॉल स्टेप में जनरेट होता है, इसलिए **कॉल-पूर्ण वेबहुक भेजने से पहले एक्सट्रैक्ट किए गए डेटा की प्रतीक्षा करें** सेटिंग (`extraction_blocks_completion`) तय करती है कि यह इवेंट इसकी प्रतीक्षा करता है या नहीं: प्रतीक्षा मोड में यह इवेंट भेजे जाने से पहले सारांश तैयार होता है (सारांश को स्टेप के 75-सेकंड बजट में अधिकतम 30 सेकंड मिलते हैं); जब इवेंट प्रतीक्षा नहीं करता (`never`, या `except_transfers` के अंतर्गत ट्रांसफ़र), तो यह फ़ील्ड `null` होती है और सारांश `call.data_extracted` में **दोबारा नहीं** भेजा जाता, इसलिए इसे बाद में [कॉल ऑब्जेक्ट](/api-reference/calls#call-object) के `summary` से पढ़ें। डैशबोर्ड में कॉल पेज पर भी उपलब्ध |
| `extracted_data` | object \| null | `status`, `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_trace` | array | केवल उन कॉल पर जिनके एजेंट के पास स्टेप्स या हैंडऑफ़्स हों। कॉल इनके माध्यम से जिस क्रम में चली: हर एंट्री में `t_ms` (कॉल शुरू होने के बाद के मिलीसेकंड), `agent` (एजेंट की कुंजी, उसकी ID के साथ `a`), `step` (स्टेप ID, या बिना स्टेप्स वाले एजेंट को हैंडऑफ़ पर `null`) और `via` (`start`, `model`, `condition`, `on_enter` या `handoff`) होते हैं। इसमें केवल ID और समय होते हैं, इसलिए रिकॉर्डिंग बंद होने पर भी इसे रखा जाता है |
| `collected_values` | object | केवल उन कॉल पर जिनके एजेंट के पास स्टेप्स या हैंडऑफ़्स हों। एजेंट ने अपने स्टेप्स में जो वैल्यू एकत्र कीं, वे कलेक्ट फ़ील्ड नाम से कुंजीबद्ध होती हैं, साथ ही प्रत्येक स्टेप के `on_enter_tool` परिणाम के लिए `<tool>_result` होता है। कॉल की सामग्री संरक्षित न होने पर छोड़ा जाता है (रिकॉर्डिंग बंद) |

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

| मान | अर्थ |
|-------|---------|
| `user_hangup` | रिमोट पार्टी ने पहले कॉल काट दिया |
| `ai_hangup` | AI ने जानबूझकर कॉल समाप्त किया |
| `ai_transfer` | AI ने कॉल ट्रांसफ़र किया; `transfer_number` सेट है |
| `ai_warm_transfer` | AI ने वॉर्म (अटेंडेड) ट्रांसफ़र पूरा किया |
| `voicemail_hangup` | वॉइसमेल का पता चला और आपके `voicemail_action` के अनुसार कॉल समाप्त किया गया |
| `max_duration` | कॉल अधिकतम अवधि सीमा तक पहुंच गई: एजेंट की या कॉल की अपनी सीमा, या 6 घंटे की प्लेटफ़ॉर्म सीमा। समय सीमा चेतावनी के बाद AI या कॉलर द्वारा समाप्त की गई कॉल में `ai_hangup` या `user_hangup` बना रहता है |
| `superseded` | सेशन को एक नए सेशन से बदल दिया गया |
| `inbound_config_webhook_failed` | वेबहुक मोड में किसी नंबर पर इनबाउंड कॉल: [कॉन्फ़िगरेशन वेबहुक](/hi/webhooks/call-incoming) ने उपयोग योग्य कॉन्फ़िगरेशन नहीं लौटाया, इसलिए एजेंट के जवाब देने से पहले कॉल समाप्त कर दिया गया। `status` `failed` है और कॉल का बिल नहीं किया जाता |
| `unknown` | समाप्ति का कारण निर्धारित नहीं किया जा सका |

## ट्रांसक्रिप्ट प्रारूप

`transcripts` में प्रत्येक एंट्री एक संवादात्मक टर्न है। भूमिकाएँ हैं
`user` (कॉलर की आवाज़), `model` (एजेंट की आवाज़ **और** टूल कॉल),
`tool` (टूल के परिणाम), और `system` (भाषा स्विच जैसे कॉल इवेंट)।

```json
[
  {
    "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`](/api-reference/calls#get-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}` उत्तर दे सकता है।
  कोई भी अन्य उत्तर (या कोई लेगेसी वेबहुक नहीं) ट्रांसफर को आगे बढ़ने देता है।
  इसके लिए एंडपॉइंट डिलीवरी से कभी परामर्श नहीं लिया जाता।

---

## उदाहरण हैंडलर

<CodeGroup>
```python 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}
```

```javascript 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 });
  },
);
```
</CodeGroup>

---

## सामान्य उपयोग के मामले

<CardGroup cols={2}>
  <Card title="CRM इंटीग्रेशन" icon="database">
    हर कॉल का ट्रांसक्रिप्ट + रिकॉर्डिंग URL आपके ग्राहक रिकॉर्ड के साथ सेव करें।
  </Card>
  <Card title="एनालिटिक्स" icon="chart-line">
    टॉपिक मॉडलिंग, CSAT सिग्नल एक्सट्रैक्शन या ट्रांसफर-रेट मॉनिटरिंग के लिए
    ट्रांसक्रिप्ट को पाइपलाइन में स्ट्रीम करें।
  </Card>
  <Card title="क्वालिटी रिव्यू" icon="clipboard-check">
    मानवीय रिव्यू के लिए कॉल को QA टूल में खोलें, या उन्हें अपने
    इवैल्यूएशन मॉडल से चलाएं।
  </Card>
  <Card title="नोटिफिकेशन" icon="bell">
    ट्रांसफर / फेल्योर पर किसी मानवीय टीममेट को ट्रिगर करें।
  </Card>
</CardGroup>

---

## संबंधित

<CardGroup cols={2}>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/hi/webhooks/call-incoming">
    कॉल शुरू होने पर चलने वाला ब्लॉकिंग समकक्ष।
  </Card>
  <Card title="इवेंट्स कैटलॉग" icon="list" href="/hi/webhooks/events">
    अन्य इवेंट टाइप जिनकी सदस्यता आप ले सकते हैं।
  </Card>
  <Card title="कॉल हिस्ट्री API" icon="phone" href="/api-reference/calls">
    बैकफिल / रीप्ले के लिए REST के माध्यम से उपलब्ध वही डेटा।
  </Card>
</CardGroup>
