---
title: "ภาพรวม Webhook"
description: "วิธีที่ ThunderPhone ส่งเหตุการณ์แบบเรียลไทม์ วิธีตรวจสอบลายเซ็น และการเปรียบเทียบโมเดลการส่งแบบเดิมกับแบบอิงปลายทาง"
---

ThunderPhone ส่งคำขอ HTTP `POST` ไปยังเซิร์ฟเวอร์ของคุณเมื่อมีเหตุการณ์
ระหว่างการโทร เช่น สายเรียกเข้าเริ่มต้น สายสิ้นสุด การประเมินเสร็จสมบูรณ์
การแจ้งเตือนทำงาน เป็นต้น โดยมี **รูปแบบการนำส่งสองแบบ**:

<CardGroup cols={2}>
  <Card title="Webhook endpoint (แนะนำ)" icon="bolt" href="/th/webhooks/endpoints">
    รองรับหลาย URL ซีเคร็ตแยกตาม endpoint ตัวกรองเหตุการณ์แยกตาม endpoint
    และการลองส่งซ้ำอัตโนมัติ
    จัดการผ่าน `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`
  </Card>
  <Card title="Webhook แบบเดิมสำหรับ URL เดียว" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    หนึ่ง URL ต่อองค์กร รองรับเหตุการณ์ตลอดวงจรการโทร รวมถึงการแลกเปลี่ยน
    การกำหนดค่าแบบ **บล็อก**
    จัดการที่ `GET/PUT /v1/webhook`
  </Card>
</CardGroup>

เหตุการณ์ทั้งสิบประเภทใน[แค็ตตาล็อกเหตุการณ์](/th/webhooks/events)
จะถูกนำส่งผ่าน webhook endpoint เหตุการณ์ตลอดวงจรการโทรทั้งหกรายการ
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) จะถูกส่งไปยัง webhook
แบบเดิมสำหรับ URL เดียว **ด้วย** — หากคุณมีทั้ง URL แบบเดิมและ endpoint
ที่ตรงกัน คุณจะได้รับเหตุการณ์ผ่านเส้นทาง **ทั้งสอง** เส้นทาง พฤติกรรมแบบบล็อก
(การ[แลกเปลี่ยนการกำหนดค่า `telephony.incoming` / `web.incoming`](/th/webhooks/call-incoming)
และ[การส่งต่อเครื่องมือ](/th/tools/overview)ในโหมด webhook)
มีอยู่เฉพาะบนเส้นทางแบบเดิมเท่านั้น การนำส่งไปยัง endpoint ทุกครั้งเป็นการแจ้งเตือน
แบบส่งแล้วไม่รอผลลัพธ์

## รูปแบบเพย์โหลด

การนำส่งไปยัง endpoint เป็นออบเจ็กต์ JSON ที่มี `data`, `event_id` และ
`type`:

```json
{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}
```

`event_id` ไม่ซ้ำกันสำหรับแต่ละเหตุการณ์ที่ส่งออก โดยมีค่าเหมือนกันในการลองส่งซ้ำ
**และ** ในทุก endpoint ที่ได้รับเหตุการณ์ — ใช้ค่านี้กำจัดรายการซ้ำ

Webhook แบบเดิมสำหรับ URL เดียวจะส่ง `type` และ `data` เดียวกัน แต่
**ไม่มี** `event_id`:

```json
{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
```

ในการส่งผ่านเครือข่าย เนื้อหาทุกรายการจะถูกซีเรียลไลซ์แบบมาตรฐาน — คีย์เรียงตาม
ตัวอักษร ไม่มีช่องว่าง และใช้ UTF-8 ตัวอย่างที่จัดรูปแบบให้อ่านง่ายในเอกสารนี้
มีไว้เพื่อให้อ่านง่ายเท่านั้น

ดู[แค็ตตาล็อกเหตุการณ์](/th/webhooks/events)สำหรับรายการประเภทเหตุการณ์และฟิลด์
เพย์โหลดทั้งหมด

## การตรวจสอบลายเซ็น

ทุกคำขอมีลายเซ็น HMAC-SHA256 ของ **เนื้อหาคำขอแบบดิบ
ทั้งหมด** อยู่ในเฮดเดอร์ `X-ThunderPhone-Signature` คีย์สำหรับลงลายเซ็นคือ
`secret` ของเอนด์พอยต์ (หรือ `secret` ของ webhook ระดับองค์กรสำหรับ
การส่งแบบเดิม)

### ขั้นตอน

1. อ่านเนื้อหาคำขอแบบดิบ **ก่อน** ทำการแยกวิเคราะห์ใดๆ
2. คำนวณ `hmac_sha256(secret, body).hexdigest()`
3. เปรียบเทียบกับเฮดเดอร์ `X-ThunderPhone-Signature` ด้วยเวลาคงที่

เราลงลายเซ็นไบต์ที่เราส่งอย่างตรงตัว และไบต์เหล่านั้นคือ
การจัดรูปแบบ JSON มาตรฐาน (เรียงลำดับคีย์ ตัวคั่นแบบกระชับ) ดังนั้น
การตรวจสอบกับเนื้อหาแบบดิบจึงใช้ได้เสมอ และหากเฟรมเวิร์กของคุณ
ส่งให้เฉพาะ JSON ที่แยกวิเคราะห์แล้ว การจัดรูปแบบใหม่โดยเรียงลำดับคีย์และ
ใช้ตัวคั่นแบบกระชับจะสร้างไบต์ที่เหมือนกัน ทั้งสองวิธีมีอธิบายไว้ใน
[คู่มือการตรวจสอบ](/th/guides/verify-webhook-signatures)

<CodeGroup>
```python Python
import hmac
import hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature or "")

# Example Flask handler
from flask import Flask, request, abort
app = Flask(__name__)

@app.post("/thunderphone-webhook")
def handle():
    body = request.get_data()
    sig = request.headers.get("X-ThunderPhone-Signature", "")
    if not verify_signature(body, sig, WEBHOOK_SECRET):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
```

```javascript Node.js (Express)
import crypto from "node:crypto";
import express from "express";

function verifySignature(body, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  if (!signature || expected.length !== signature.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature),
  );
}

const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.header("X-ThunderPhone-Signature") || "";
    if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);
```
</CodeGroup>

## ความหมายของการส่ง

ความหมายเหล่านี้ใช้กับการส่งไปยัง **ปลายทาง** Webhook แบบ URL เดี่ยวรุ่นเก่าเป็นการพยายามแบบซิงโครนัสเพียงครั้งเดียวโดยไม่มีการลองใหม่

<AccordionGroup>
  <Accordion title="การลองใหม่">
    แต่ละเหตุการณ์จะถูกพยายามส่งทันทีหนึ่งครั้ง การตอบกลับ `2xx` ใดๆ จะยืนยันการส่ง หากเกิดผลลัพธ์อื่นใด (ไม่ใช่ 2xx ข้อผิดพลาดการเชื่อมต่อ หมดเวลา) ระบบจะลองใหม่ที่เวลา **1 นาที 5 นาที 30 นาที 2 ชั่วโมง 6 ชั่วโมง 12 ชั่วโมง และ 24 ชั่วโมงหลังการพยายามครั้งแรก** — รวม 8 ครั้งภายใน 24 ชั่วโมง หากทุกครั้งล้มเหลว ระบบจะหยุดการส่งและทำเครื่องหมายปลายทางเป็น `status="failing"` ใน [ปลายทาง Webhook](/th/webhooks/endpoints) ส่งคืน `2xx` ทันทีที่ยอมรับเพย์โหลดอย่างถาวรแล้ว และประมวลผลแบบอะซิงโครนัส
  </Accordion>

  <Accordion title="ลำดับ">
    ลำดับการส่งเป็นแบบพยายามอย่างดีที่สุด ในทางปฏิบัติ เราส่งตามลำดับที่มีการปล่อยเหตุการณ์ แต่การลองใหม่อาจทำให้ลำดับเปลี่ยนเมื่อเกิดข้อผิดพลาด ทำการขจัดข้อมูลซ้ำและกระทบยอดโดยใช้ `call_id` / รหัสออบเจ็กต์เสมอ
  </Accordion>

  <Accordion title="รายการซ้ำ">
    การส่งเป็นแบบ **อย่างน้อยหนึ่งครั้ง**: การลองใหม่หลังจากการตอบกลับที่เราไม่เคยได้รับอาจทำให้เกิดเหตุการณ์ซ้ำ การลองใหม่ทุกครั้งมี `event_id` เดียวกัน ดังนั้นให้จัดเก็บรหัสที่ประมวลผลแล้วและข้ามรายการซ้ำ `event_id` ยังใช้ร่วมกันระหว่างปลายทางด้วย — ปลายทางสองแห่งที่สมัครรับเหตุการณ์เดียวกันจะได้รับ `event_id` เดียวกัน
  </Accordion>

  <Accordion title="การหมดเวลา">
    การส่งไปยังปลายทางมีเวลาหมดเวลา **30 วินาที** ต่อการพยายามหนึ่งครั้ง สำหรับเส้นทางรุ่นเก่า คำขอแบบบล็อกที่กำหนดพฤติกรรมของสายสด — การแลกเปลี่ยนการกำหนดค่า [`telephony.incoming` / `web.incoming`](/th/webhooks/call-incoming) — จะหมดเวลาหลัง **10 วินาที** แต่การตอบกลับที่ช้าจะทำให้การรับสายล่าช้า ดังนั้นควรตอบกลับภายในไม่กี่วินาที โหมด Webhook ของ[การเรียกใช้เครื่องมือ](/th/tools/overview)อนุญาต 20 วินาทีโดยค่าเริ่มต้น และการประกาศเครื่องมือสามารถกำหนด `timeout` ระดับบนสุดได้
  </Accordion>

  <Accordion title="IP ต้นทาง">
    Webhook ขาออกมาจากช่วง IP บนคลาวด์ของ ThunderPhone หากไฟร์วอลล์ของคุณต้องใช้รายการที่อนุญาต โปรดติดต่อฝ่ายสนับสนุน แล้วเราจะแชร์ช่วงปัจจุบันให้
  </Accordion>
</AccordionGroup>

## การเลือกระหว่าง Webhook รุ่นเก่าและ Webhook แบบปลายทาง

| ฟีเจอร์ | รุ่นเก่า (`/v1/webhook`) | ปลายทาง (`/v1/developer/webhook-endpoints`) |
|---------|------------------------|----------------------------------------------|
| จำนวน URL | 1 ต่อองค์กร | หลายรายการต่อองค์กร |
| ขอบเขตเหตุการณ์ | เฉพาะ `telephony.*` / `web.*` | เหตุการณ์ทั้ง 10 ประเภท |
| ตัวกรองเหตุการณ์ | — | ต่อปลายทาง |
| การลองใหม่ | ไม่มี | 8 ครั้งภายใน 24 ชั่วโมง |
| ซองข้อมูล | `type` + `data` | `type` + `data` + `event_id` |
| การหมุนเวียนข้อมูลลับ | แทนที่ข้อมูลลับเดี่ยว | ข้อมูลลับต่อปลายทาง |
| ปิดใช้งานโดยไม่ลบ | `PUT /v1/webhook` พร้อม `{"url": ""}` | `status=disabled` |
| การมองเห็นสถานะ | — | `active` / `disabled` / `failing` |
| การแลกเปลี่ยนการกำหนดค่าแบบบล็อก | ใช่ ([`telephony.incoming` / `web.incoming`](/th/webhooks/call-incoming)) | ไม่มี — การแจ้งเตือนเท่านั้น |
| เหมาะที่สุดสำหรับ | การกำหนดค่าสายแบบไดนามิก | การรับเหตุการณ์ในระบบใช้งานจริง |

การผสานรวมใหม่ควรรับเหตุการณ์ผ่าน Webhook แบบปลายทาง เก็บ URL รุ่นเก่าไว้ (หรือเพิ่ม URL รุ่นเก่า) เฉพาะเมื่อคุณกำหนดค่าสายแบบไดนามิกขณะรับสาย หรือใช้การเรียกใช้เครื่องมือโหมด Webhook — การแลกเปลี่ยนคำขอ/การตอบกลับเหล่านั้นทำงานได้เฉพาะบนเส้นทางรุ่นเก่า

---

## ที่เกี่ยวข้อง

<CardGroup cols={2}>
  <Card title="แค็ตตาล็อกเหตุการณ์" icon="list" href="/th/webhooks/events">
    เหตุการณ์ทุกประเภทและเพย์โหลดของเหตุการณ์เหล่านั้น
  </Card>
  <Card title="ปลายทาง Webhook" icon="bolt" href="/th/webhooks/endpoints">
    จัดการปลายทางหลายรายการ ตัวกรองเหตุการณ์ และข้อมูลลับ
  </Card>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/th/webhooks/call-incoming">
    คำขอแบบบล็อกที่เซิร์ฟเวอร์ของคุณต้องตอบเพื่อกำหนดค่าสาย
  </Card>
  <Card title="telephony.complete / web.complete" icon="phone" href="/th/webhooks/call-complete">
    เพย์โหลดหลังวางสายพร้อมข้อความถอดเสียง การบันทึก และเมตริก
  </Card>
</CardGroup>
