Open in
ภาพรวม Webhook
วิธีที่ ThunderPhone ส่งเหตุการณ์แบบเรียลไทม์ วิธีตรวจสอบลายเซ็น และการเปรียบเทียบโมเดลการส่งแบบเดิมกับแบบอิงปลายทาง
ThunderPhone ส่งคำขอ HTTP POST ไปยังเซิร์ฟเวอร์ของคุณเมื่อมีเหตุการณ์
ระหว่างการโทร เช่น สายเรียกเข้าเริ่มต้น สายสิ้นสุด การประเมินเสร็จสมบูรณ์
การแจ้งเตือนทำงาน เป็นต้น โดยมี รูปแบบการนำส่งสองแบบ:
รองรับหลาย URL ซีเคร็ตแยกตาม endpoint ตัวกรองเหตุการณ์แยกตาม endpoint
และการลองส่งซ้ำอัตโนมัติ
จัดการผ่าน GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints
หนึ่ง URL ต่อองค์กร รองรับเหตุการณ์ตลอดวงจรการโทร รวมถึงการแลกเปลี่ยน
การกำหนดค่าแบบ บล็อก
จัดการที่ GET/PUT /v1/webhook
เหตุการณ์ทั้งสิบประเภทในแค็ตตาล็อกเหตุการณ์
จะถูกนำส่งผ่าน webhook endpoint เหตุการณ์ตลอดวงจรการโทรทั้งหกรายการ
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) จะถูกส่งไปยัง webhook
แบบเดิมสำหรับ URL เดียว ด้วย — หากคุณมีทั้ง URL แบบเดิมและ endpoint
ที่ตรงกัน คุณจะได้รับเหตุการณ์ผ่านเส้นทาง ทั้งสอง เส้นทาง พฤติกรรมแบบบล็อก
(การแลกเปลี่ยนการกำหนดค่า telephony.incoming / web.incoming
และการส่งต่อเครื่องมือในโหมด webhook)
มีอยู่เฉพาะบนเส้นทางแบบเดิมเท่านั้น การนำส่งไปยัง endpoint ทุกครั้งเป็นการแจ้งเตือน
แบบส่งแล้วไม่รอผลลัพธ์
รูปแบบเพย์โหลด
การนำส่งไปยัง endpoint เป็นออบเจ็กต์ JSON ที่มี data, event_id และ
type:
{
"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:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}ในการส่งผ่านเครือข่าย เนื้อหาทุกรายการจะถูกซีเรียลไลซ์แบบมาตรฐาน — คีย์เรียงตาม ตัวอักษร ไม่มีช่องว่าง และใช้ UTF-8 ตัวอย่างที่จัดรูปแบบให้อ่านง่ายในเอกสารนี้ มีไว้เพื่อให้อ่านง่ายเท่านั้น
ดูแค็ตตาล็อกเหตุการณ์สำหรับรายการประเภทเหตุการณ์และฟิลด์ เพย์โหลดทั้งหมด
การตรวจสอบลายเซ็น
ทุกคำขอมีลายเซ็น HMAC-SHA256 ของ เนื้อหาคำขอแบบดิบ
ทั้งหมด อยู่ในเฮดเดอร์ X-ThunderPhone-Signature คีย์สำหรับลงลายเซ็นคือ
secret ของเอนด์พอยต์ (หรือ secret ของ webhook ระดับองค์กรสำหรับ
การส่งแบบเดิม)
ขั้นตอน
- อ่านเนื้อหาคำขอแบบดิบ ก่อน ทำการแยกวิเคราะห์ใดๆ
- คำนวณ
hmac_sha256(secret, body).hexdigest() - เปรียบเทียบกับเฮดเดอร์
X-ThunderPhone-Signatureด้วยเวลาคงที่
เราลงลายเซ็นไบต์ที่เราส่งอย่างตรงตัว และไบต์เหล่านั้นคือ การจัดรูปแบบ JSON มาตรฐาน (เรียงลำดับคีย์ ตัวคั่นแบบกระชับ) ดังนั้น การตรวจสอบกับเนื้อหาแบบดิบจึงใช้ได้เสมอ และหากเฟรมเวิร์กของคุณ ส่งให้เฉพาะ JSON ที่แยกวิเคราะห์แล้ว การจัดรูปแบบใหม่โดยเรียงลำดับคีย์และ ใช้ตัวคั่นแบบกระชับจะสร้างไบต์ที่เหมือนกัน ทั้งสองวิธีมีอธิบายไว้ใน คู่มือการตรวจสอบ
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 "", 204import 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);
},
);ความหมายของการส่ง
ความหมายเหล่านี้ใช้กับการส่งไปยัง ปลายทาง Webhook แบบ URL เดี่ยวรุ่นเก่าเป็นการพยายามแบบซิงโครนัสเพียงครั้งเดียวโดยไม่มีการลองใหม่
การลองใหม่
แต่ละเหตุการณ์จะถูกพยายามส่งทันทีหนึ่งครั้ง การตอบกลับ 2xx ใดๆ จะยืนยันการส่ง หากเกิดผลลัพธ์อื่นใด (ไม่ใช่ 2xx ข้อผิดพลาดการเชื่อมต่อ หมดเวลา) ระบบจะลองใหม่ที่เวลา 1 นาที 5 นาที 30 นาที 2 ชั่วโมง 6 ชั่วโมง 12 ชั่วโมง และ 24 ชั่วโมงหลังการพยายามครั้งแรก — รวม 8 ครั้งภายใน 24 ชั่วโมง หากทุกครั้งล้มเหลว ระบบจะหยุดการส่งและทำเครื่องหมายปลายทางเป็น status="failing" ใน ปลายทาง Webhook ส่งคืน 2xx ทันทีที่ยอมรับเพย์โหลดอย่างถาวรแล้ว และประมวลผลแบบอะซิงโครนัส
ลำดับ
ลำดับการส่งเป็นแบบพยายามอย่างดีที่สุด ในทางปฏิบัติ เราส่งตามลำดับที่มีการปล่อยเหตุการณ์ แต่การลองใหม่อาจทำให้ลำดับเปลี่ยนเมื่อเกิดข้อผิดพลาด ทำการขจัดข้อมูลซ้ำและกระทบยอดโดยใช้ call_id / รหัสออบเจ็กต์เสมอ
รายการซ้ำ
การส่งเป็นแบบ อย่างน้อยหนึ่งครั้ง: การลองใหม่หลังจากการตอบกลับที่เราไม่เคยได้รับอาจทำให้เกิดเหตุการณ์ซ้ำ การลองใหม่ทุกครั้งมี event_id เดียวกัน ดังนั้นให้จัดเก็บรหัสที่ประมวลผลแล้วและข้ามรายการซ้ำ event_id ยังใช้ร่วมกันระหว่างปลายทางด้วย — ปลายทางสองแห่งที่สมัครรับเหตุการณ์เดียวกันจะได้รับ event_id เดียวกัน
การหมดเวลา
การส่งไปยังปลายทางมีเวลาหมดเวลา 30 วินาที ต่อการพยายามหนึ่งครั้ง สำหรับเส้นทางรุ่นเก่า คำขอแบบบล็อกที่กำหนดพฤติกรรมของสายสด — การแลกเปลี่ยนการกำหนดค่า telephony.incoming / web.incoming — จะหมดเวลาหลัง 10 วินาที แต่การตอบกลับที่ช้าจะทำให้การรับสายล่าช้า ดังนั้นควรตอบกลับภายในไม่กี่วินาที โหมด Webhook ของการเรียกใช้เครื่องมืออนุญาต 20 วินาทีโดยค่าเริ่มต้น และการประกาศเครื่องมือสามารถกำหนด timeout ระดับบนสุดได้
IP ต้นทาง
Webhook ขาออกมาจากช่วง IP บนคลาวด์ของ ThunderPhone หากไฟร์วอลล์ของคุณต้องใช้รายการที่อนุญาต โปรดติดต่อฝ่ายสนับสนุน แล้วเราจะแชร์ช่วงปัจจุบันให้
การเลือกระหว่าง 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) | ไม่มี — การแจ้งเตือนเท่านั้น |
| เหมาะที่สุดสำหรับ | การกำหนดค่าสายแบบไดนามิก | การรับเหตุการณ์ในระบบใช้งานจริง |
การผสานรวมใหม่ควรรับเหตุการณ์ผ่าน Webhook แบบปลายทาง เก็บ URL รุ่นเก่าไว้ (หรือเพิ่ม URL รุ่นเก่า) เฉพาะเมื่อคุณกำหนดค่าสายแบบไดนามิกขณะรับสาย หรือใช้การเรียกใช้เครื่องมือโหมด Webhook — การแลกเปลี่ยนคำขอ/การตอบกลับเหล่านั้นทำงานได้เฉพาะบนเส้นทางรุ่นเก่า
ที่เกี่ยวข้อง
เหตุการณ์ทุกประเภทและเพย์โหลดของเหตุการณ์เหล่านั้น
จัดการปลายทางหลายรายการ ตัวกรองเหตุการณ์ และข้อมูลลับ
คำขอแบบบล็อกที่เซิร์ฟเวอร์ของคุณต้องตอบเพื่อกำหนดค่าสาย
เพย์โหลดหลังวางสายพร้อมข้อความถอดเสียง การบันทึก และเมตริก