ThunderPhone 2.0 yayında.Hemen kendiniz kullanmaya başlayın; dakikası 2 sentten başlayan fiyatlarla.Duyuruyu okuyun

Developer cookbook

Çağrı başına dinamik yapılandırma

Kontrol ettiğiniz bir webhook

Varsayılan olarak her telefon numarasına ve yayınlanabilir anahtara statik bir ajan atanır. Arayan bazında veya ziyaretçi bazında özelleştirme gerektiğinde — VIP yönlendirmesi, oturum açmış kullanıcı bağlamı, A/B istem testleri — webhook moduna geçin ve karar vermeyi sunucunuza bırakın.

Nasıl çalışır

  1. telephony.incoming (telefon) veya web.incoming (widget) etkinliğine abone olun. Her ikisi de engelleyici webhook'lardır: ThunderPhone, aramaya devam etmeden önce yanıtınızı bekler; telefon araması için 13 saniyeye kadar (bir yeniden deneme dahil), widget oturumu için ise 10 saniye bekler.
  2. ThunderPhone size {call_id, from_number, to_number} gönderir (widget oturumları numaralar yerine widget'a özgü alanlar içerir — bkz. istek şeması).
  3. Sunucunuz bir ajan yapılandırmasıyla yanıt verir (istem, ses, ürün, araçlar). ThunderPhone bu yapılandırmayı arama için kullanır.
  4. Yedek ajan yoktur. {} veya geçersiz bir yapılandırma döndürürseniz ya da istek yeniden deneme sonrasında da başarısız olursa, telefon araması hemen sonlandırılır ve end_reason değeri inbound_config_webhook_failed olarak kaydedilir; widget oturumu isteği başarısız olur.

1. Webhook hedefini yapılandırın

Telefon numaraları için uç noktanızı telephony.incoming etkinliğine abone edin:

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"]
  }'

Yanıt tek kullanımlık bir secret içerir — kaydedin; bunu imza doğrulaması için kullanacaksınız.

Widget oturumları için uç nokta URL'niz yerleşik olan mode="webhook" modunda bir yayınlanabilir anahtar oluşturun:

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"]
  }'

Widget, her oturum başlangıcında bu URL'ye POST isteği gönderir.

2. İşleyiciyi uygulayın

Üç temel kural:

  • Her istekte imzayı doğrulayın (bkz. Webhook imzalarını doğrulama). Geliştirme ortamında bunu atlamayın — bir kez doğru uygulayın ve yeniden kullanın.
  • Hızlı yanıt verin. Bir telefon görüşmesinin ilk denemesi 10 saniyede, tüm etkileşim ise 13 saniyede kesilir; her saniye arayan için sessizlik demektir. Gerekirse veritabanı sorguları yapın, ancak alt sistemlerdeki LLM'leri eşzamanlı çağırmayın — dinamik istem oluşturmak istiyorsanız önceden hesaplayın ve önbelleğe alın.
  • Temiz şekilde geri dönüş yapın. Beklenmeyen herhangi bir durumda, varsayılan bir prompt ve voice (veya kaydedilmiş bir agent_id) ile yanıt verin. {} veya hata durumu döndürmek görüşmeyi sonlandırır.
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. Yanıt şeması

Yanıt gövdesi, gelen çağrı yanıt şemasıyla tam olarak eşleşir. Sık kullanılan alanlar:

AlanTürAçıklama
promptdize (zorunlu)Ajan için sistem istemi
voicedize (zorunlu)GET /v1/voices içinden ses kimliği
productdizeVarsayılan değer spark olur. Deneysel ürünler yalnızca izin verilen kuruluşlar için kullanılabilir.
background_trackdize | nullOrtam sesi kimliği
acknowledgement_prompt_modedizeauto veya manual (yalnızca Storm-with-ack)
acknowledgement_promptdizeMod manual olduğunda zorunludur
toolsdiziSatır içi işlev aracı şemaları — bkz. İşlev Araçları

Kaydedilmiş bir ajanı koruyun ve değişken sağlayın

Çağrı başına verilerle kuruluşun kaydedilmiş ajanını kullanmak için {"agent_id": 12, "variables": {"name": "Ada"}} döndürün. İstemi {{name}} veya {{name|Friend}} içerebilir. Webhook değişkenleri istek düzeyindeki değişkenlerle birleştirilir; null yer tutucunun varsayılan değerini veya belirtilmemişse boş metni kullanır. Nihai değerler ve çözümlenmemiş adlar çağrı ayrıntılarında ve tamamlama webhook'larında görünür. Kaydedilmiş ajan yanıtları yalnızca agent_id, variables ve timezone kabul eder. prompt mevcutsa yanıt satır içi yapılandırmayı kullanır ve agent_id değerini yok sayar ( null veya tam sayı olmayan meta veriler dahil); satır içi istem yine de geçerli olmalıdır. Satır içi yapılandırma yanıtları variables da içerebilir. Kaydedilmiş ajan yanıtları hem telefon hem de widget çağrılarında ajanın yayına alınmış A/B dağılımını kullanır, ardından değişkenleri işler. Sınırlar ve oturum API desteği için çağrı değişkenleri bölümüne bakın. Engelleyici yapılandırma, eski telefon numarası/kuruluş URL'sinden veya webhook modundaki bir widget anahtarından gelir; uç nokta sistemine gelen olaylar yalnızca bildirimdir.

Her arama için saat dilimini ayarlayın

Tek bir yapay zeka ajanı, farklı saat dilimlerindeki kişiler için yanıt verebilir. Kaydedilmiş bir ajana veya satır içi yanıta IANA adı olan timezone ekleyin; ajan bu arama için tarihi ve saati belirtir ve ajanın kendi saat diliminden önce bu saat dilimine göre saat seçenekleri sunar:

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

variables içinde zaman damgası yerine saat dilimini gönderin: ajanın saati arama boyunca çalışmaya devam eder ve ilettiğiniz saat güncelliğini yitirir. IANA saat dilimi olmayan bir değer yok sayılır ve arama ajanın saat diliminde bağlanır.

Kalıplar

Oturum açmış kullanıcı bağlamı

Webhook modundaki widget'larda ziyaretçinin sayfası, ziyaretçinin kim olduğunu zaten bilir. Widget SDK'sının ilettiği bir sorgu dizesi parametresiyle (?customer_id=123) webhook'unuzu çağırın ve müşteriyi sunucu tarafında arayın.

A/B istem yayına alma

Bunu kendiniz geliştirmeden önce, ThunderPhone'un varyantları tanımlayan, trafiği bölen ve varyant başına sonuçları karşılaştıran yerel bir Deneyler özelliği bulunduğunu unutmayın (/dashboard/experiments ve ajan oluşturucunun A/B sekmesi) — webhook gerekmez.

Yine de webhook tarafında kontrol gerekiyorsa: call_id değerini karmalayın → gruba atayın; 0..49 için istem A'yı, 50..99 için istem B'yi sunun. Seçtiğiniz grubu kendi veritabanınıza kaydedin ve daha sonra tamamlanan çağrının puanıyla ilişkilendirin.

Zamana dayalı yönlendirme

Mesai saatleri → "canlı destek" ajanı; mesai dışı → "mesaj alın" ajanı. İşleyicinizde new Date().getUTCHours() üzerinde basit bir geçiş uygulayın.


Sonraki adımlar