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

Developer cookbook

डायनेमिक प्रति-कॉल कॉन्फ़िगरेशन

आपके नियंत्रित वेबहुक में कस्टम लॉजिक द्वारा संचालित, हर इनकमिंग कॉल के लिए अलग से उत्तर देने वाला एजेंट चुनें — या उसका प्रॉम्प्ट और सेटिंग्स फिर से लिखें।

डिफ़ॉल्ट रूप से हर फ़ोन नंबर और पब्लिशेबल की को एक स्टैटिक एजेंट असाइन किया जाता है। जब आपको हर कॉलर के लिए या हर विज़िटर के लिए कस्टमाइज़ेशन चाहिए — VIP रूटिंग, लॉग-इन किए हुए यूज़र का कॉन्टेक्स्ट, A/B प्रॉम्प्ट टेस्ट — तो वेबहुक मोड पर स्विच करें और अपने सर्वर को निर्णय लेने दें।

यह कैसे काम करता है

  1. आप telephony.incoming (फोन) या web.incoming (विजेट) इवेंट की सदस्यता लेते हैं। दोनों ब्लॉकिंग वेबहुक हैं: ThunderPhone कॉल जारी रखने से पहले आपके रिस्पॉन्स का इंतज़ार करता है—फोन कॉल के लिए अधिकतम 13 सेकंड (एक रीट्राई सहित) और विजेट सेशन के लिए 10 सेकंड।
  2. ThunderPhone आपको {call_id, from_number, to_number} भेजता है (विजेट सेशन में नंबरों के बजाय विजेट-विशिष्ट फ़ील्ड होते हैं—रिक्वेस्ट स्कीमा देखें)।
  3. आपका सर्वर एजेंट कॉन्फ़िगरेशन (प्रॉम्प्ट, वॉइस, प्रोडक्ट, टूल्स) के साथ रिस्पॉन्स देता है। ThunderPhone कॉल के लिए उस कॉन्फ़िगरेशन का उपयोग करता है।
  4. कोई फ़ॉलबैक एजेंट नहीं है। यदि आप {} या अमान्य कॉन्फ़िगरेशन लौटाते हैं, या रीट्राई के बाद भी रिक्वेस्ट विफल रहती है, तो फोन कॉल तुरंत समाप्त कर दी जाती है और end_reason inbound_config_webhook_failed के साथ रिकॉर्ड की जाती है; विजेट सेशन रिक्वेस्ट विफल हो जाती है।

1. वेबहुक डेस्टिनेशन कॉन्फ़िगर करें

फ़ोन नंबरों के लिए, अपने एंडपॉइंट को telephony.incoming के लिए सब्सक्राइब करें:

curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'

रिस्पॉन्स में एक वन-शॉट secret शामिल होता है — इसे सेव करें; आप इसका उपयोग सिग्नेचर वेरिफ़िकेशन के लिए करेंगे।

विजेट सेशन के लिए, अपने एंडपॉइंट URL के साथ mode="webhook" में एक पब्लिशेबल की बनाएँ:

curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'

हर सेशन शुरू होने पर विजेट इस URL पर POST करेगा।

2. हैंडलर लागू करें

तीन सामान्य नियम:

  • हर अनुरोध पर सिग्नेचर सत्यापित करें (देखें वेबहुक सिग्नेचर सत्यापित करें). डेवलपमेंट में इसे न छोड़ें — इसे एक बार सही करें और दोबारा इस्तेमाल करें।
  • तेज़ी से जवाब दें। फ़ोन कॉल का पहला प्रयास 10 सेकंड पर काट दिया जाता है और पूरा एक्सचेंज 13 सेकंड पर, और हर सेकंड कॉलर के लिए खामोशी होता है। ज़रूरत हो तो डेटाबेस लुकअप करें, लेकिन डाउनस्ट्रीम LLMs को सिंक्रोनस रूप से कॉल न करें — अगर आपको डायनेमिक प्रॉम्प्ट जनरेशन चाहिए, तो पहले से कंप्यूट करें और कैश करें।
  • साफ़ तरीके से फ़ॉलबैक करें। किसी भी अप्रत्याशित स्थिति के लिए, डिफ़ॉल्ट prompt और voice (या सेव किया हुआ agent_id) के साथ जवाब दें। {} या एरर स्टेटस लौटाने से कॉल समाप्त हो जाती है।
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, sig: str) -> bool:
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig or "")
 
@app.post("/thunderphone/incoming")
async def incoming(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(401)
 
    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default
 
    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs
 
def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
Express
import crypto from "node:crypto";
import express from "express";
 
const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;
 
function verify(body, sig) {
  const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
  return sig &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
 
app.post(
  "/thunderphone/incoming",
  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"));
 
    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});
 
    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);

3. रिस्पॉन्स स्कीमा

रिस्पॉन्स बॉडी इनकमिंग-कॉल रिस्पॉन्स स्कीमा से बिल्कुल मेल खाती है। आम तौर पर उपयोग किए जाने वाले फ़ील्ड:

फ़ील्डटाइपविवरण
promptस्ट्रिंग (आवश्यक)एजेंट के लिए सिस्टम प्रॉम्प्ट
voiceस्ट्रिंग (आवश्यक)GET /v1/voices से वॉइस आईडी
productस्ट्रिंगडिफ़ॉल्ट रूप से spark। एक्सपेरिमेंटल प्रोडक्ट केवल अलाउलिस्ट किए गए संगठनों के लिए उपलब्ध हैं।
background_trackस्ट्रिंग | nullएम्बिएंट ऑडियो आईडी
acknowledgement_prompt_modeस्ट्रिंगauto या manual (केवल ack वाले Storm के लिए)
acknowledgement_promptस्ट्रिंगमोड manual होने पर आवश्यक
language_switchingस्ट्रिंगबहुभाषी एजेंटों के लिए, ask_first या follow_caller
toolsऐरेइनलाइन फंक्शन-टूल स्कीमा — फंक्शन टूल्स देखें

सेव किया हुआ एजेंट रखें और वेरिएबल्स दें

उस संगठन के सेव किए गए एजेंट को प्रति-कॉल डेटा के साथ उपयोग करने के लिए {"agent_id": 12, "variables": {"name": "Ada"}} लौटाएँ। इसके प्रॉम्प्ट में {{name}} या {{name|Friend}} हो सकता है। वेबहुक वेरिएबल्स अनुरोध-स्तरीय वेरिएबल्स के ऊपर मर्ज होते हैं; null प्लेसहोल्डर डिफ़ॉल्ट का उपयोग करता है, या यदि कोई डिफ़ॉल्ट नहीं दिया गया है तो खाली टेक्स्ट का। अंतिम वैल्यू और अनरिज़ॉल्व्ड नाम कॉल विवरण और कंप्लीशन वेबहुक्स में दिखाई देते हैं। सेव किए गए एजेंट के रिस्पॉन्स केवल agent_id, variables और timezone स्वीकार करते हैं। यदि prompt मौजूद है, तो रिस्पॉन्स इनलाइन कॉन्फ़िगरेशन का उपयोग करता है और agent_id को अनदेखा करता है (जिसमें null या गैर-पूर्णांक मेटाडेटा शामिल है); इनलाइन प्रॉम्प्ट का फिर भी वैध होना आवश्यक है। इनलाइन कॉन्फ़िगरेशन रिस्पॉन्स में variables भी शामिल हो सकते हैं। सेव किए गए एजेंट के रिस्पॉन्स फ़ोन और विजेट, दोनों कॉल्स पर एजेंट के डिप्लॉय किए गए A/B स्प्लिट का उपयोग करते हैं, फिर वेरिएबल्स रेंडर करते हैं। सीमाओं और सेशन API समर्थन के लिए कॉल वेरिएबल्स देखें। ब्लॉकिंग कॉन्फ़िगरेशन लेगेसी फ़ोन-नंबर/संगठन URL या वेबहुक-मोड विजेट की से आता है; एंडपॉइंट-सिस्टम इनकमिंग इवेंट्स केवल नोटिफ़िकेशन हैं।

हर कॉल के लिए टाइम ज़ोन सेट करें

एक एजेंट अलग-अलग टाइम ज़ोन में मौजूद कॉन्टैक्ट्स के लिए जवाब दे सकता है। किसी सेव किए गए एजेंट या इनलाइन रिस्पॉन्स में IANA नाम timezone जोड़ें। एजेंट इस कॉल के लिए तारीख और समय बताता है और एजेंट के अपने टाइम ज़ोन से पहले, उस ज़ोन के समय ऑफ़र करता है:

{"agent_id": 12, "timezone": "America/Chicago", "variables": {"name": "Ada"}}

variables में टाइमस्टैम्प के बजाय ज़ोन भेजें: कॉल के दौरान एजेंट की घड़ी चलती रहती है, और आपके द्वारा भेजा गया समय पुराना हो जाता है। जो वैल्यू IANA ज़ोन नहीं है उसे अनदेखा कर दिया जाता है और कॉल एजेंट के ज़ोन में कनेक्ट होती है।

पैटर्न

लॉग-इन उपयोगकर्ता कॉन्टेक्स्ट

वेबहुक-मोड विजेट्स में, विज़िटर का पेज पहले से जानता है कि वे कौन हैं। अपने वेबहुक को उस क्वेरी स्ट्रिंग पैरामीटर के साथ कॉल करें जिसे विजेट SDK फॉरवर्ड करता है (?customer_id=123) और ग्राहक को सर्वर-साइड पर लुक अप करें।

A/B प्रॉम्प्ट रोलआउट

इसे स्वयं बनाने से पहले, ध्यान दें कि ThunderPhone में एक नेटिव एक्सपेरिमेंट्स फ़ीचर (/dashboard/experiments और एजेंट बिल्डर का A/B टैब) है, जो वैरिएंट्स परिभाषित करता है, ट्रैफ़िक विभाजित करता है, और प्रत्येक वैरिएंट के परिणामों की तुलना करता है — वेबहुक की आवश्यकता नहीं है।

यदि फिर भी आपको वेबहुक-साइड कंट्रोल चाहिए: call_id → बकेट हैश करें; 0..49 के लिए प्रॉम्प्ट A और 50..99 के लिए प्रॉम्प्ट B दें। चुने गए बकेट को अपने DB में रिकॉर्ड करें और बाद में पूर्ण हुई कॉल के ग्रेड से कोरिलेट करें।

समय-आधारित रूटिंग

बिज़नेस आवर्स → "लाइव सपोर्ट" एजेंट; आफ्टर-आवर्स → "संदेश लें" एजेंट। अपने हैंडलर में new Date().getUTCHours() पर प्योर स्विच करें।


अगले चरण