Το ThunderPhone 2.0 είναι εδώ.Ξεκινήστε μόνοι σας, από 2¢/λεπτό.Διαβάστε την ανακοίνωση

Developer cookbook

Δυναμική διαμόρφωση ανά κλήση

Επιλέξτε τον πράκτορα που απαντά — ή επαναδιατυπώστε το prompt και τις ρυθμίσεις του — ξεχωριστά για κάθε εισερχόμενη κλήση, με βάση προσαρμοσμένη λογική σε ένα webhook που ελέγχετε.

Από προεπιλογή, σε κάθε αριθμό τηλεφώνου και δημοσιεύσιμο κλειδί έχει αντιστοιχιστεί ένας στατικός πράκτορας. Όταν χρειάζεστε προσαρμογή ανά καλούντα ή ανά επισκέπτη — δρομολόγηση VIP, περιβάλλον συνδεδεμένου χρήστη, δοκιμές προτροπών A/B — μεταβείτε σε λειτουργία webhook και αφήστε τον διακομιστή σας να αποφασίσει.

Πώς λειτουργεί

  1. Εγγράφεστε στο συμβάν telephony.incoming (τηλέφωνο) ή web.incoming (widget). Και τα δύο είναι δεσμευτικά webhooks: το ThunderPhone περιμένει έως 10 δευτερόλεπτα για την απάντησή σας πριν συνεχίσει την κλήση.
  2. Το ThunderPhone σάς στέλνει {call_id, from_number, to_number} (οι συνεδρίες widget περιλαμβάνουν πεδία ειδικά για widget αντί για αριθμούς — δείτε το σχήμα αιτήματος).
  3. Ο διακομιστής σας απαντά με μια διαμόρφωση πράκτορα (προτροπή, φωνή, προϊόν, εργαλεία). Το ThunderPhone χρησιμοποιεί αυτή τη διαμόρφωση για την κλήση.
  4. Αν επιστρέψετε {}, προκύψει χρονικό όριο ή σφάλμα, χρησιμοποιείται ως εναλλακτική ο στατικά αντιστοιχισμένος πράκτορας. Ασφαλής προεπιλογή.

1. Διαμορφώστε τον προορισμό webhook

Για αριθμούς τηλεφώνου, εγγράψτε το τελικό σημείο σας στο 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 μίας χρήσης — αποθηκεύστε το· θα το χρησιμοποιήσετε για επαλήθευση υπογραφής.

Για συνεδρίες widget, δημιουργήστε ένα δημοσιεύσιμο κλειδί σε mode="webhook" με τη διεύθυνση URL του τελικού σημείου σας ενσωματωμένη:

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 θα στέλνει POST σε αυτή τη διεύθυνση URL σε κάθε έναρξη συνεδρίας.

2. Υλοποιήστε τον χειριστή

Τρεις πρακτικοί κανόνες:

  • Επαληθεύετε την υπογραφή σε κάθε αίτημα (βλ. Επαλήθευση υπογραφών webhook). Μην το παραλείπετε στο περιβάλλον ανάπτυξης — υλοποιήστε το σωστά μία φορά και επαναχρησιμοποιήστε το.
  • Απαντάτε γρήγορα. Τα δέκα δευτερόλεπτα είναι το αυστηρό όριο και κάθε δευτερόλεπτο είναι νεκρός χρόνος για τον καλούντα. Κάντε αναζητήσεις στη βάση δεδομένων αν χρειάζεται, αλλά μην καλείτε κατάντη LLM συγχρονισμένα — αν θέλετε δυναμική δημιουργία προτροπών, προϋπολογίστε και αποθηκεύστε στην κρυφή μνήμη.
  • Εφαρμόζετε καθαρή εναλλακτική λύση. Κάθε μη αναμενόμενη κατάσταση πρέπει να επιστρέφει {}, ώστε ο στατικά εκχωρημένος πράκτορας να διαχειρίζεται την κλήση.
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 (μόνο Storm με επιβεβαίωση)
acknowledgement_promptσυμβολοσειράΥποχρεωτικό όταν η λειτουργία είναι manual
toolsπίνακαςΕνσωματωμένα σχήματα εργαλείων συναρτήσεων — δείτε Εργαλεία συναρτήσεων

Διατηρήστε έναν αποθηκευμένο πράκτορα και παρέχετε μεταβλητές

Επιστρέψτε {"agent_id": 12, "variables": {"name": "Ada"}} για να χρησιμοποιήσετε τον αποθηκευμένο πράκτορα του οργανισμού με δεδομένα ανά κλήση. Η προτροπή του μπορεί να περιέχει {{name}} ή {{name|Friend}}. Οι μεταβλητές του άγκιστρου ιστού συγχωνεύονται πάνω από τις μεταβλητές επιπέδου αιτήματος· το null χρησιμοποιεί την προεπιλεγμένη τιμή του συμβόλου κράτησης ή κενό κείμενο εάν δεν παρέχεται καμία. Οι τελικές τιμές και τα μη επιλυμένα ονόματα εμφανίζονται στις λεπτομέρειες κλήσης και στα άγκιστρα ιστού ολοκλήρωσης. Οι αποκρίσεις αποθηκευμένου πράκτορα δέχονται μόνο agent_id και variables. Εάν υπάρχει το prompt, η απόκριση χρησιμοποιεί ενσωματωμένη διαμόρφωση και αγνοεί το agent_id (συμπεριλαμβανομένων μεταδεδομένων null ή μη ακέραιου αριθμού)· η ενσωματωμένη προτροπή πρέπει και πάλι να είναι έγκυρη. Οι αποκρίσεις ενσωματωμένης διαμόρφωσης μπορούν επίσης να περιλαμβάνουν variables. Οι αποκρίσεις αποθηκευμένου πράκτορα χρησιμοποιούν τη δημοσιευμένη κατανομή A/B του πράκτορα τόσο σε τηλεφωνικές κλήσεις όσο και σε κλήσεις γραφικού στοιχείου και, στη συνέχεια, αποδίδουν τις μεταβλητές. Δείτε τις μεταβλητές κλήσης για όρια και υποστήριξη API συνεδρίας. Η διαμόρφωση αποκλεισμού προέρχεται από το παλαιό URL αριθμού τηλεφώνου/οργανισμού ή από ένα κλειδί γραφικού στοιχείου σε λειτουργία άγκιστρου ιστού· τα εισερχόμενα συμβάντα τελικού σημείου-συστήματος είναι μόνο ειδοποιήσεις.

Πρότυπα

Πλαίσιο συνδεδεμένου χρήστη

Στα γραφικά στοιχεία σε λειτουργία άγκιστρου ιστού, η σελίδα του επισκέπτη γνωρίζει ήδη ποιος είναι. Καλέστε το άγκιστρο ιστού σας με μια παράμετρο συμβολοσειράς ερωτήματος που το SDK του γραφικού στοιχείου προωθεί (?customer_id=123) και αναζητήστε τον πελάτη στην πλευρά του διακομιστή.

Κυκλοφορία προτροπών A/B

Πριν το υλοποιήσετε χειροκίνητα, σημειώστε ότι το ThunderPhone διαθέτει εγγενή λειτουργία Πειραμάτων (/dashboard/experiments και την καρτέλα A/B του εργαλείου δημιουργίας πρακτόρων) που ορίζει παραλλαγές, κατανέμει την κίνηση και συγκρίνει αποτελέσματα ανά παραλλαγή — δεν απαιτείται άγκιστρο ιστού.

Εάν χρειάζεστε οπωσδήποτε έλεγχο από την πλευρά του άγκιστρου ιστού: κατακερματίστε το call_id → κάδος· εξυπηρετήστε την προτροπή A για 0..49 και την προτροπή B για 50..99. Καταγράψτε τον κάδο που επιλέξατε στη δική σας ΒΔ και αργότερα συσχετίστε τον με τον βαθμό της ολοκληρωμένης κλήσης.

Δρομολόγηση βάσει χρόνου

Ώρες λειτουργίας → πράκτορας «ζωντανής υποστήριξης»· εκτός ωραρίου → πράκτορας «λήψης μηνύματος». Απλή εναλλαγή στο new Date().getUTCHours() στον χειριστή σας.


Επόμενα βήματα