---
title: "การกำหนดค่าแบบไดนามิกสำหรับแต่ละสาย"
description: "เลือกเอเจนต์ที่รับสาย หรือเขียนพรอมต์และการตั้งค่าใหม่ แยกสำหรับสายเรียกเข้าทุกสาย โดยขับเคลื่อนด้วยตรรกะแบบกำหนดเองในเว็บฮุกที่คุณควบคุม"
---

โดยค่าเริ่มต้น หมายเลขโทรศัพท์และ publishable key ทุกอันจะมีเอเจนต์แบบคงที่
กำหนดไว้ เมื่อคุณต้องการปรับแต่งแบบ **รายผู้โทร** หรือ **รายผู้เข้าชม**
— การกำหนดเส้นทาง VIP บริบทผู้ใช้ที่เข้าสู่ระบบ การทดสอบพรอมป์แบบ A/B — ให้เปลี่ยนเป็น
โหมด webhook และให้เซิร์ฟเวอร์ของคุณเป็นผู้ตัดสินใจ

## วิธีการทำงาน

1. คุณสมัครรับเหตุการณ์ [`telephony.incoming`](/th/webhooks/events)
   (โทรศัพท์) หรือ [`web.incoming`](/th/webhooks/events) (วิดเจ็ต)
   ทั้งสองเป็น webhook แบบ **บล็อกการทำงาน**: ThunderPhone จะรอสูงสุด
   10 วินาทีเพื่อรับการตอบกลับของคุณก่อนดำเนินการโทรต่อ
2. ThunderPhone จะส่ง `{call_id, from_number, to_number}` ให้คุณ (เซสชันวิดเจ็ต
   จะมีฟิลด์เฉพาะวิดเจ็ตแทนหมายเลข — ดู
   [สคีมาคำขอ](/th/webhooks/call-incoming))
3. เซิร์ฟเวอร์ของคุณตอบกลับด้วยการกำหนดค่าเอเจนต์ (พรอมป์ เสียง
   ผลิตภัณฑ์ เครื่องมือ) ThunderPhone จะใช้การกำหนดค่านั้นสำหรับสาย
4. หากคุณส่งกลับ `{}` หมดเวลา หรือเกิดข้อผิดพลาด
   จะใช้เอเจนต์ที่กำหนดแบบคงที่เป็นทางเลือกสำรอง ค่าเริ่มต้นที่ปลอดภัย

<Note>
  ใช้งานเหมือนกันสำหรับสายโทรศัพท์ (`telephony.incoming`) และเซสชันวิดเจ็ต
  (`web.incoming`) ไม่ว่าจะส่งไปยังปลายทาง webhook
  หรือ webhook URL เดียวแบบเดิม
</Note>

<Warning>
  **จะไม่มีการเล่นประกาศขอความยินยอมของ ThunderPhone สำหรับสายที่กำหนดค่าผ่าน webhook แบบอินไลน์**
  เส้นทางนี้จะข้ามประกาศเมื่อเริ่มสายระดับเอเจนต์
  และถูกยกเว้นอย่างชัดเจนจากกรอบการประกาศขอความยินยอมของ ThunderPhone
  (ข้อกำหนดการให้บริการ ส่วน "การบันทึกและความยินยอม") องค์กรของคุณ
  รับผิดชอบประกาศและความยินยอมทั้งหมดเกี่ยวกับการบันทึก การติดตาม การมีส่วนร่วมของ AI
  และการระบุตัวผู้โทรสำหรับสายเหล่านี้ จัดเตรียมประกาศและความยินยอมเหล่านี้ในขั้นตอนของคุณเอง
  — ตัวอย่างเช่น ในสคริปต์เปิดของพรอมป์ —
  ก่อนเปิดใช้โหมด webhook แบบอินไลน์ การตอบกลับ webhook ที่อ้างอิง
  `agent_id` ที่บันทึกไว้จะใช้นโยบายการบันทึกและการเปิดเผยข้อมูลตามปกติของเอเจนต์นั้น
</Warning>

## 1. กำหนดค่าปลายทาง webhook

<Tabs>
<Tab title="สายโทรศัพท์">
สำหรับหมายเลขโทรศัพท์ ให้สมัครรับ `telephony.incoming` ที่ปลายทางของคุณ:

```bash
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="วิดเจ็ตเว็บ">
สำหรับเซสชันวิดเจ็ต ให้สร้าง publishable key ใน `mode="webhook"`
โดยฝัง URL ปลายทางของคุณไว้:

```bash
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. ใช้งานตัวจัดการ

หลักปฏิบัติ 3 ข้อ:

- **ตรวจสอบลายเซ็น** ในทุกคำขอ (ดู
  [ตรวจสอบลายเซ็น webhook](/th/guides/verify-webhook-signatures))
  อย่าข้ามขั้นตอนนี้ในระหว่างพัฒนา — ทำให้ถูกต้องครั้งเดียวแล้วนำกลับมาใช้ซ้ำ
- **ตอบกลับอย่างรวดเร็ว** สิบวินาทีคือขีดจำกัดสูงสุด และทุกวินาทีคือ
  ช่วงเงียบสำหรับผู้โทร คุณสามารถค้นหาข้อมูลจากฐานข้อมูลได้หากจำเป็น แต่
  อย่าเรียก LLM ปลายทางแบบซิงโครนัส — หากต้องการสร้างพรอมป์ต์แบบไดนามิก ให้คำนวณล่วงหน้าและแคชไว้
- **สำรองการทำงานอย่างเรียบร้อย** สถานะที่ไม่คาดคิดใดๆ ควรส่งคืน `{}` เพื่อให้
  เอเจนต์ที่กำหนดแบบคงที่จัดการสาย

<CodeGroup>
```python 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
```

```javascript 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
  },
);
```
</CodeGroup>

## 3. สคีมาการตอบกลับ

เนื้อหาการตอบกลับตรงกับ
[สคีมาการตอบกลับสายเรียกเข้า](/th/webhooks/call-incoming)
ทุกประการ ฟิลด์ที่ใช้บ่อยมีดังนี้:

| ฟิลด์ | ประเภท | คำอธิบาย |
|-------|------|-------------|
| `prompt` | สตริง (จำเป็น) | พรอมป์ตระบบสำหรับเอเจนต์ |
| `voice` | สตริง (จำเป็น) | รหัสเสียงจาก [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | สตริง | ค่าเริ่มต้นคือ `spark` |
| `background_track` | สตริง \| null | รหัสเสียงบรรยากาศ |
| `acknowledgement_prompt_mode` | สตริง | `auto` หรือ `manual` (เฉพาะ Storm พร้อมการตอบรับ) |
| `acknowledgement_prompt` | สตริง | จำเป็นเมื่อโหมดเป็น `manual` |
| `tools` | อาร์เรย์ | สคีมาเครื่องมือฟังก์ชันแบบอินไลน์ — ดู [เครื่องมือฟังก์ชัน](/th/tools/overview) |

<Note>
  ลำดับการพูดต่อสายและ `max_hold_seconds` ไม่พร้อมใช้งานใน
  การตอบกลับของ webhook กำหนดค่าเหล่านี้ใน
  [เอเจนต์](/api-reference/agents) ที่คุณอ้างอิง
</Note>

### เก็บเอเจนต์ที่บันทึกไว้และส่งตัวแปร

ส่งคืน `{"agent_id": 12, "variables": {"name": "Ada"}}` เพื่อใช้เอเจนต์ที่บันทึกไว้ขององค์กรนั้น
พร้อมข้อมูลต่อสาย พรอมป์ตของเอเจนต์สามารถมี `{{name}}` หรือ
`{{name|Friend}}` ได้ ตัวแปรจาก Webhook จะรวมทับตัวแปรระดับคำขอ ค่า null
จะใช้ค่าเริ่มต้นของตัวยึดตำแหน่ง หรือข้อความว่างหากไม่มีการระบุ ค่า
สุดท้ายและชื่อที่ยังไม่ได้แก้ไขจะแสดงในรายละเอียดการโทรและ webhook เมื่อเสร็จสิ้น
การตอบกลับจากเอเจนต์ที่บันทึกไว้รับเฉพาะ `agent_id` และ `variables` หากมี `prompt`
การตอบกลับจะใช้การกำหนดค่าแบบอินไลน์และไม่สนใจ `agent_id` (รวมถึง
ข้อมูลเมตาที่เป็น null หรือไม่ใช่จำนวนเต็ม) โดยพรอมป์ตแบบอินไลน์ยังต้องถูกต้อง
การตอบกลับการกำหนดค่าแบบอินไลน์อาจมี `variables` ได้เช่นกัน การตอบกลับจากเอเจนต์ที่บันทึกไว้จะใช้
การแบ่ง A/B ที่ปรับใช้ของเอเจนต์ทั้งกับสายโทรศัพท์และสายวิดเจ็ต จากนั้นจึงแสดงผลตัวแปร ดู [ตัวแปรการโทร](/th/guides/call-variables)
สำหรับข้อจำกัดและการรองรับ API เซสชัน การกำหนดค่าการบล็อกมาจาก URL แบบเดิมของ
หมายเลขโทรศัพท์/องค์กร หรือคีย์วิดเจ็ตโหมด webhook ส่วนเหตุการณ์ขาเข้าของระบบปลายทาง
เป็นเพียงการแจ้งเตือนเท่านั้น

## รูปแบบ

### บริบทผู้ใช้ที่เข้าสู่ระบบ

ในวิดเจ็ตโหมด webhook หน้าของผู้เยี่ยมชมทราบอยู่แล้วว่า
พวกเขาเป็นใคร เรียก webhook ของคุณด้วยพารามิเตอร์สตริงคำค้นหาที่ SDK ของวิดเจ็ต
ส่งต่อ (`?customer_id=123`) แล้วค้นหาข้อมูลลูกค้าฝั่งเซิร์ฟเวอร์

### การทยอยเปิดใช้พรอมป์ตแบบ A/B

ก่อนทำสิ่งนี้เอง โปรดทราบว่า ThunderPhone มีฟีเจอร์
[การทดสอบ](/th/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="/th/webhooks/call-incoming">
    สคีมาคำขอและการตอบกลับแบบครบถ้วน รวมถึงคีย์การกำหนดค่าทุกรายการ
  </Card>
  <Card title="ตรวจสอบลายเซ็น webhook" icon="shield-check" href="/th/guides/verify-webhook-signatures">
    ตั้งค่า HMAC ให้ถูกต้องครั้งเดียว แล้วใช้ซ้ำได้ทุกที่
  </Card>
  <Card title="สร้างการผสานรวมเครื่องมือ" icon="screwdriver-wrench" href="/th/guides/build-tool-integration">
    รวมการกำหนดเส้นทางแบบไดนามิกเข้ากับเครื่องมือเฉพาะเอเจนต์
  </Card>
  <Card title="ความหมายของการส่งมอบ" icon="bolt" href="/th/webhooks/overview">
    การลองใหม่ ลำดับ เวลาเกินกำหนด
  </Card>
</CardGroup>
