> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# תצורה דינמית לכל שיחה

> בחרו סוכן — או כתבו מחדש הנחיה — לכל שיחה נכנסת, על סמך לוגיקה מותאמת אישית ב-webhook.

כברירת מחדל, לכל מספר טלפון ומפתח ציבורי מוקצה סוכן סטטי.
כשאתם זקוקים להתאמה אישית **לכל מתקשר** או **לכל מבקר**
— ניתוב VIP, הקשר של משתמש מחובר, בדיקות A/B של הנחיות — עברו למצב
webhook ותנו לשרת שלכם להחליט.

## איך זה עובד

1. הירשמו לאירוע [`telephony.incoming`](/he/webhooks/events)
   (טלפון) או [`web.incoming`](/he/webhooks/events) (ווידג'ט).
   שניהם webhooks **חוסמים**: ThunderPhone ממתין עד
   10 שניות לתגובה שלכם לפני המשך השיחה.
2. ThunderPhone שולח לכם את `{call_id, from_number, to_number}` (סשנים של
   וידג'ט כוללים שדות ייעודיים לווידג'ט במקום מספרים — ראו את
   [סכמת הבקשה](/he/webhooks/call-incoming)).
3. השרת שלכם מגיב עם תצורת סוכן (הנחיה, קול,
   מוצר, כלים). ThunderPhone משתמש בתצורה הזו עבור השיחה.
4. אם תחזירו `{}`, תחרגו מזמן ההמתנה או תתקבל שגיאה, ייעשה שימוש בסוכן
   שהוקצה סטטית כגיבוי. ברירת מחדל בטוחה.

<Note>
  פועל באופן זהה עבור שיחות טלפון (`telephony.incoming`) וסשנים של
  וידג'ט (`web.incoming`), בין אם הם נשלחים לנקודת קצה של webhook
  או ל-webhook הישן בעל כתובת URL יחידה.
</Note>

## 1. הגדירו את יעד ה-webhook

<Tabs>
  <Tab title="שיחות טלפון">
    עבור מספרי טלפון, הירשמו עם נקודת הקצה שלכם ל-`telephony.incoming`:

    ```bash theme={null}
    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` חד-פעמי — שמרו אותו; תשתמשו בו
    לאימות חתימה.
  </Tab>

  <Tab title="וידג'ט אינטרנט">
    עבור סשנים של וידג'ט, צרו מפתח ציבורי ב-`mode="webhook"`
    כשכתובת ה-URL של נקודת הקצה שלכם מוטמעת בו:

    ```bash theme={null}
    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"]
      }'
    ```

    הווידג'ט ישלח POST לכתובת URL זו בתחילת כל סשן.
  </Tab>
</Tabs>

## 2. הטמיעו את המטפל

שלושה כללי אצבע:

* **אמתו את החתימה** בכל בקשה (ראו
  [אימות חתימות webhook](/he/guides/verify-webhook-signatures)).
  אל תדלגו על כך בפיתוח — עשו זאת נכון פעם אחת והשתמשו מחדש.
* **הגיבו במהירות**. עשר שניות הן הגבול הקשיח, וכל שנייה היא
  שקט מת עבור המתקשר. בצעו חיפושים במסד הנתונים אם צריך, אך
  אל תקראו למודלי שפה גדולים במורד הזרם באופן סינכרוני — אם אתם רוצים יצירה דינמית של הנחיות, חשבו מראש ושמרו במטמון.
* **בצעו נסיגה בצורה נקייה**. כל מצב בלתי צפוי צריך להחזיר `{}` כדי
  שהסוכן שהוקצה באופן סטטי יטפל בשיחה.

<CodeGroup>
  ```python FastAPI theme={null}
  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
  ```

  ```javascript Express theme={null}
  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
    },
  );
  ```
</CodeGroup>

## 3. סכמת תגובה

גוף התגובה תואם בדיוק את
[סכמת התגובה לשיחה נכנסת](/he/webhooks/call-incoming).
השדות הנפוצים:

| שדה                           | סוג            | תיאור                                                              |
| ----------------------------- | -------------- | ------------------------------------------------------------------ |
| `prompt`                      | מחרוזת (נדרש)  | הנחיית מערכת עבור הסוכן                                            |
| `voice`                       | מחרוזת (נדרש)  | מזהה קול מתוך [`GET /v1/voices`](/api-reference/agents#voices)     |
| `product`                     | מחרוזת         | ברירת המחדל היא `spark`                                            |
| `background_track`            | מחרוזת \| null | מזהה שמע סביבתי                                                    |
| `acknowledgement_prompt_mode` | מחרוזת         | `auto` או `manual` (Storm-with-ack בלבד)                           |
| `acknowledgement_prompt`      | מחרוזת         | נדרש כאשר המצב הוא `manual`                                        |
| `tools`                       | מערך           | סכמות כלי-פונקציה מוטמעות — ראו [כלי פונקציות](/he/tools/overview) |

<Note>
  סדר הדיבור לכל שיחה ו-`max_hold_seconds` אינם זמינים בתגובת
  ה-webhook. הגדירו אותם ב-[סוכן](/api-reference/agents) שאליו אתם מפנים.
</Note>

## דפוסים

### הקשר של משתמש מחובר

בווידג'טים במצב webhook, דף המבקר כבר יודע מי הוא. קראו ל-webhook שלכם עם פרמטר מחרוזת שאילתה שה-SDK של הווידג'ט מעביר הלאה (`?customer_id=123`) וחפשו את הלקוח בצד השרת.

### השקת A/B של הנחיות

לפני שתממשו זאת ידנית, שימו לב של-ThunderPhone יש יכולת מובנית של [ניסויים](/he/guides/concepts)
(`/dashboard/experiments` ולשונית **A/B** בבונה הסוכנים) שמגדירה וריאציות, מפצלת תעבורה ומשווה תוצאות לכל וריאציה —
ללא צורך ב-webhook.

אם בכל זאת אתם זקוקים לשליטה בצד ה-webhook: בצעו גיבוב של `call_id` → קטגוריה;
החזירו הנחיה A עבור `0..49` והנחיה B עבור `50..99`. תעדו במסד הנתונים שלכם
את הקטגוריה שבחרתם, ולאחר מכן בצעו התאמה מול ציון השיחה שהושלמה.

### ניתוב מבוסס זמן

שעות פעילות → סוכן "תמיכה חיה"; מחוץ לשעות הפעילות → סוכן "קבלת הודעה".
מתג פשוט המבוסס על `new Date().getUTCHours()` בפונקציית הטיפול שלכם.

***

## השלבים הבאים

<CardGroup cols={2}>
  <Card title="הפניית webhook לשיחות נכנסות" icon="phone" href="/he/webhooks/call-incoming">
    סכמות מדויקות של בקשות ותגובות, כולל כל מפתח תצורה.
  </Card>

  <Card title="אימות חתימות webhook" icon="shield-check" href="/he/guides/verify-webhook-signatures">
    הגדירו את ה-HMAC נכון פעם אחת; השתמשו בו מחדש בכל מקום.
  </Card>

  <Card title="בניית שילוב כלי" icon="screwdriver-wrench" href="/he/guides/build-tool-integration">
    שלבו ניתוב דינמי עם כלים לכל סוכן.
  </Card>

  <Card title="סמנטיקת מסירה" icon="bolt" href="/he/webhooks/overview">
    ניסיונות חוזרים, סדר, פסקי זמן.
  </Card>
</CardGroup>
