telephony.incoming / web.incoming
যখন একটি ইনবাউন্ড ফোন কল কোনো নির্ধারিত
এজেন্ট ছাড়া একটি নম্বরে পৌঁছায়, অথবা একটি ওয়েব উইজেট সেশন
mode="webhook"-এ একটি প্রকাশযোগ্য কী দিয়ে শুরু হয়, তখন ThunderPhone আপনার
লিগ্যাসি webhook URL-এ একটি ব্লকিং
telephony.incoming / web.incoming অনুরোধ পাঠায় এবং কনফিগারেশন উত্তরের জন্য সর্বোচ্চ 10 সেকেন্ড অপেক্ষা করে। প্রতি কলে গতিশীলভাবে একটি prompt, ভয়েস এবং টুল বেছে নিতে এই
বিনিময়টি ব্যবহার করুন — সম্পূর্ণ ধাপের জন্য গতিশীল কল কনফিগারেশন নির্দেশিকা
দেখুন।
ব্লকিং বিনিময়ের কোনো fallback নেই: আপনার handler যদি একটি
non-2xx স্ট্যাটাস ফেরত দেয়, সময় শেষ করে, বা যাচাইকরণে ব্যর্থ এমন একটি config ফেরত দেয়,
তাহলে কলটি প্রত্যাখ্যাত হয় (ফোন কল সংযুক্ত হয় না; উইজেট
সেশন অনুরোধ 502/422 দিয়ে ব্যর্থ হয়)। দ্রুত উত্তর দিন — আপনি সিদ্ধান্ত নেওয়ার সময় কলার রিংব্যাক শুনছে।
অনুরোধের পেলোড
ফোন কলের জন্য (telephony.incoming):
{
"type": "telephony.incoming",
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}
| ক্ষেত্র | ধরন | বিবরণ |
|---|---|---|
call_id | integer | কল id — এই কলের সব ইভেন্টে স্থিতিশীল |
from_number | string | E.164 কলার নম্বর |
to_number | string | E.164 গন্তব্য (আপনার ThunderPhone নম্বরগুলোর একটি) |
ওয়েব উইজেট সেশনের জন্য (web.incoming), data ফোন নম্বরের পরিবর্তে
এমবেড করা পৃষ্ঠাটিকে শনাক্ত করে:
{
"type": "web.incoming",
"data": {
"call_id": 987654322,
"origin_domain": "https://example.com",
"publishable_key_prefix": "pk_live_a1b2"
}
}
| ক্ষেত্র | ধরন | বিবরণ |
|---|---|---|
call_id | integer | কল id |
origin_domain | string | উইজেট হোস্ট করা পৃষ্ঠার origin |
publishable_key_prefix | string | সেশনটি খোলা প্রকাশযোগ্য কীর প্রথম অক্ষরগুলো |
language, primary_language | string | উইজেট সেশন একটি ভাষা override অনুরোধ করলে উপস্থিত থাকে |
voice | string | উইজেট সেশন একটি ভয়েস override অনুরোধ করলে উপস্থিত থাকে |
website_context | string | উইজেট প্রতি-সেশন পৃষ্ঠা context পাঠালে উপস্থিত থাকে |
প্রতিক্রিয়া স্কিমা
এই কলের জন্য এজেন্ট কনফিগারেশন বর্ণনা করে এমন একটি JSON অবজেক্ট ফেরত দিন।
prompt এবং voice আবশ্যক; অন্য সবকিছু ঐচ্ছিক।
{
"prompt": "You are a helpful booking assistant for Acme Restaurant.",
"voice": "john",
"product": "spark",
"background_track": null,
"tools": []
}
| ক্ষেত্র | ধরন | আবশ্যক | বিবরণ |
|---|---|---|---|
prompt | string | হ্যাঁ | এজেন্টকে পরিচালনাকারী সিস্টেম prompt |
voice | string | হ্যাঁ | GET /v1/voices থেকে ভয়েস id, যেমন john। voice_name একটি বিকল্প নাম হিসেবে গ্রহণ করা হয়। অজানা ভয়েস ভ্যালিডেশনে ব্যর্থ হয় এবং কল প্রত্যাখ্যান করে |
product | string | না | ডিফল্ট spark। অনুমোদিত: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | string | না | minimal, base (ডিফল্ট), অথবা extra। Storm প্রোডাক্টের জন্য ওভাররাইড হয়: storm-extra* extra বাধ্যতামূলক করে, অন্য storm-* base বাধ্যতামূলক করে |
audio_context_mode | string | না | full (ডিফল্ট) অথবা reduced |
watchdog_enabled | boolean | না | এই কলের জন্য তত্ত্বাবধান সক্রিয় করুন। ডিফল্ট false |
storm_feedback_mode | string | না | none, acknowledgement (ডিফল্ট), অথবা tick |
language | string | না | primary_language-এর সংক্ষিপ্ত রূপ |
primary_language | string | না | ভাষার কোড, স্বাভাবিকীকৃত (ডিফল্ট en)। নির্ধারণ করা যায় না এমন কোড কল প্রত্যাখ্যান করে |
has_additional_languages | boolean | না | ডিফল্ট false |
additional_languages | array of string | না | এজেন্ট যে অতিরিক্ত ভাষাগুলোতে পরিবর্তন করতে পারে |
background_track | string | null | না | পরিবেশের অডিও id অথবা null |
acknowledgement_prompt_mode | string | না | auto (ডিফল্ট) অথবা manual (Storm-with-ack প্রোডাক্ট) |
acknowledgement_prompt | string | না | acknowledgement_prompt_mode="manual" হলে ব্যবহৃত হয় |
silence_interval_seconds | integer | null | না | 5–120। কলারের নীরবতার পর চেক-ইনের আগের সেকেন্ড |
silence_max_checkins | integer | null | না | 1–10 |
silence_checkins_enabled | boolean | না | ডিফল্ট true |
connect_tone_enabled | boolean | না | ডিফল্ট false |
voicemail_action | string | না | prompt (ডিফল্ট), hangup, অথবা message |
voicemail_message | string | না | voicemail_action="message" হলে ব্যবহৃত হয় |
agent_name | string | না | ড্যাশবোর্ড এবং উইজেটে প্রদর্শিত নাম |
org_name | string | না | এজেন্টের ব্যক্তিত্বের জন্য প্রতিষ্ঠানের প্রদর্শিত নাম |
tools | array | না | ইনলাইন ফাংশন-টুল স্কিমা (Function Tools দেখুন) |
call_id | integer | না | অনুরোধের call id-এর ঐচ্ছিক প্রতিলিপি; উপেক্ষা করা হয় |
যেহেতু prompt এবং voice আবশ্যক, {} অথবা ভ্যালিডেশনে ব্যর্থ কোনো
প্রতিক্রিয়া ফেরত দিলে 422 সহ কল প্রত্যাখ্যান করা হয় — এই পথে কোনো
স্ট্যাটিক-এজেন্ট ফলব্যাক নেই (webhook মোডে একটি নম্বর বা কী-এর জন্য কোনো
নির্ধারিত এজেন্ট থাকে না)।
প্রতিক্রিয়ার আকারের সীমা
উদাহরণ হ্যান্ডলার
import hashlib
import hmac
import json
import os
from fastapi import FastAPI, HTTPException, Request
app = FastAPI()
WEBHOOK_SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
def verify(body: bytes, signature: str) -> bool:
expected = hmac.new(WEBHOOK_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"] == "telephony.incoming":
caller = event["data"]["from_number"]
prompt = (
"Greet the caller as a San Francisco local…"
if caller.startswith("+1415")
else "You are a friendly customer support agent…"
)
return {
"prompt": prompt,
"voice": "john",
"product": "spark",
}
if event["type"] == "web.incoming":
return {
"prompt": "You are the website's helpful voice assistant…",
"voice": "john",
"product": "spark",
}
return {}
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" }),
(req, res) => {
if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString("utf8"));
if (event.type === "telephony.incoming" || event.type === "web.incoming") {
const caller = event.data.from_number || "web";
const prompt = caller.startsWith("+1415")
? "Greet the caller as a San Francisco local…"
: "You are a friendly customer support agent…";
return res.json({
prompt,
voice: "john",
product: "spark",
});
}
res.json({});
},
);
ফাংশন টুলসহ প্রতিক্রিয়া
টুল সংযুক্ত করুন, যাতে AI কথোপকথনের মাঝখানে আপনার API কল করতে পারে:
{
"prompt": "You are a booking assistant. Use the available tools to help customers schedule appointments.",
"voice": "john",
"product": "spark",
"tools": [
{
"type": "function",
"function": {
"name": "search_appointments",
"description": "Find available appointment slots",
"parameters": {
"type": "object",
"properties": {
"date": { "type": "string", "description": "YYYY-MM-DD" },
"service": { "type": "string" }
},
"required": ["date"]
}
},
"endpoint": {
"url": "https://api.example.com/appointments/search",
"method": "POST",
"headers": {
"X-Api-Key": "your-key"
}
}
}
]
}
প্রোডাক্ট স্তরের সংক্ষিপ্ত তালিকা
| প্রোডাক্ট | লেটেন্সি | রিজনিং | স্বীকৃতি |
|---|---|---|---|
spark | সর্বনিম্ন | প্রাথমিক | — |
bolt | কম | উন্নত | — |
storm-base | মাঝারি | শক্তিশালী | — |
storm-base-with-ack | মাঝারি | শক্তিশালী | চিন্তার সময় স্বয়ংক্রিয় ফিলার |
storm-extra | বেশি | গভীর | — |
storm-extra-with-ack | বেশি | গভীর | চিন্তার সময় স্বয়ংক্রিয় ফিলার |