Usanidi unaobadilika kwa kila simu

Kwa chaguo-msingi, kila nambari ya simu na ufunguo unaoweza kuchapishwa una ejenti tuli iliyowekwa. Unapohitaji ubinafsishaji kwa kila mpigaji au kwa kila mgeni — uelekezaji wa VIP, muktadha wa mtumiaji aliyeingia, majaribio ya prompt ya A/B — badilisha hadi hali ya webhook na uruhusu seva yako iamue.

Jinsi inavyofanya kazi

  1. Unajiandikisha kwa tukio la telephony.incoming (simu) au web.incoming (wijeti). Zote ni webhook zinazosubiri: ThunderPhone husubiri hadi sekunde 10 kwa jibu lako kabla ya kuendelea na simu.
  2. ThunderPhone hukutumia {call_id, from_number, to_number} (vipindi vya wijeti hubeba sehemu mahususi za wijeti badala ya nambari — tazama schema ya ombi).
  3. Seva yako hujibu kwa usanidi wa ejenti (prompt, sauti, bidhaa, zana). ThunderPhone hutumia usanidi huo kwa simu.
  4. Ukirejesha {}, muda ukiisha, au hitilafu ikitokea, ejenti iliyowekwa tuli hutumika kama mbadala. Chaguo-msingi salama.

1. Sanidi eneo la webhook

Simu

Kwa nambari za simu, jiandikishe endpoint yako kwa 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"]
  }'

Jibu linajumuisha secret ya matumizi ya mara moja — ihifadhi; utaitumia kwa uthibitishaji wa sahihi.

Wijeti ya wavuti

Kwa vipindi vya wijeti, unda ufunguo unaoweza kuchapishwa katika mode="webhook" ukiwa na URL ya endpoint yako iliyojumuishwa:

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

Wijeti itatuma POST kwa URL hii kila kipindi kinapoanza.

2. Tekeleza kishughulikiaji

Kanuni tatu za kukumbuka:

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
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. Schema ya jibu

Mwili wa jibu unalingana na schema ya jibu la simu inayoingia hasa. Sehemu zinazotumiwa mara nyingi:

SehemuAinaMaelezo
promptstring (inahitajika)System prompt ya ejenti
voicestring (inahitajika)Kitambulisho cha sauti kutoka GET /v1/voices
productstringChaguo-msingi ni spark
background_trackstring | nullKitambulisho cha sauti ya mazingira
acknowledgement_prompt_modestringauto au manual (Storm-with-ack pekee)
acknowledgement_promptstringInahitajika wakati modi ni manual
toolsarraySchema za inline za function-tool — angalia Function Tools

Miundo

Muktadha wa mtumiaji aliyeingia

Katika wijeti za hali ya webhook, ukurasa wa mgeni tayari unajua yeye ni nani. Piga webhook yako kwa kutumia kigezo cha mfuatano wa hoja ambacho SDK ya wijeti hutuma mbele (?customer_id=123) na umtafute mteja upande wa seva.

Utoaji wa prompt wa A/B

Kabla hujatengeneza hii mwenyewe, kumbuka kwamba ThunderPhone ina kipengele asilia cha Majaribio (/dashboard/experiments na kichupo cha A/B cha kijenzi cha ejenti) kinachofafanua vibadala, kugawa trafiki, na kulinganisha matokeo kwa kila kibadala — hakuna webhook inayohitajika.

Ikiwa bado unahitaji udhibiti wa upande wa webhook: fanya hash ya call_id → bucket; toa prompt A kwa 0..49 na prompt B kwa 50..99. Rekodi bucket uliyochagua kwenye DB yako mwenyewe na baadaye uihusishe na daraja la simu iliyokamilika.

Uelekezaji kulingana na muda

Saa za kazi → ejenti wa "msaada wa moja kwa moja"; baada ya saa za kazi → ejenti wa "chukua ujumbe" . Badiliko rahisi kulingana na new Date().getUTCHours() katika handler yako.


Hatua zinazofuata