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

Webhooks

telephony.complete / web.complete

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

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

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

  • Webhook एंडपॉइंट्स को telephony.complete (फोन कॉल) या web.complete (वेब कॉल और बिल्डर माइक टेस्ट कॉल) मिलता है, जिसमें नीचे डॉक्यूमेंट किया गया स्टेबल पेलोड, प्रत्येक डिलीवरी का event_id, 30 s टाइमआउट और 24 h तक रिट्राई शामिल होते हैं।
  • लेगसी सिंगल-URL वेबहुक को थोड़ा अलग पेलोड के साथ एक सिंक्रोनस प्रयास मिलता है (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",
    "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,
    "voice": "john"
  },
  "event_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
  "type": "telephony.complete"
}
फ़ील्डटाइपविवरण
call_idइंटीजरइस कॉल के प्रत्येक इवेंट में स्टेबल रहता है
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स्ट्रिंगकॉल के समय प्रभावी एजेंट कॉन्फ़िग
transfer_numberस्ट्रिंग | nullकॉल ट्रांसफर होने पर सेट होता है
recording_urlस्ट्रिंग | nullएक्सपायर होने वाला साइन किया गया URL; तुरंत डाउनलोड करें। रिकॉर्डिंग आर्टिफैक्ट उपलब्ध न होने पर null
billable_minutesनंबरबिल किए गए मिनट, निकटतम क्वार्टर मिनट तक राउंड किए गए (15-सेकंड इंक्रीमेंट, न्यूनतम 0.25)। सीधे वॉइसमेल पर जाने वाली कॉल यहां भी अपने वास्तविक मीटर्ड मिनट रिपोर्ट करती हैं, लेकिन प्लान रेट पर शुल्क एक मिनट तक सीमित होता है।
billing_total_centsइंटीजरUSD सेंट
transcriptsऐरेप्रत्येक टर्न की ट्रांसक्रिप्ट एंट्री; ट्रांसक्रिप्ट उपलब्ध न होने पर खाली हो सकता है

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

वैल्यूअर्थ
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, जब प्रति-टर्न रिकॉर्ड किए गए हों

पूरी तरह स्ट्रक्चर्ड टर्न हिस्ट्री (इंटरप्शन मार्कर, ack-प्रॉम्प्ट और रॉ पोज़िशन के साथ) के लिए, GET /v1/calls/{call_id}/history का उपयोग करें।

लेगेसी पेलोड अंतर

लेगेसी सिंगल-URL वेबहुक एनवेलप है {"type": "telephony.complete" | "web.complete", "data": {…}}, जिसमें कोई event_id नहीं होता, और इसका data एंडपॉइंट पेलोड से अलग होता है:

  • टर्न ऐरे 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 टूल में खोलें, या उन्हें अपने मूल्यांकन मॉडल से चलाएं।

नोटिफिकेशन

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


संबंधित