ภาพรวม Webhook

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

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

รูปแบบ payload

การส่งข้อมูลไปยัง 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 ตัวอย่างที่จัดรูปแบบให้อ่านง่ายใน เอกสารนี้มีไว้เพื่อความสะดวกในการอ่านเท่านั้น

ดูแค็ตตาล็อกเหตุการณ์สำหรับรายการประเภทเหตุการณ์และ ฟิลด์ payload ทั้งหมด

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

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

ขั้นตอน

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

เราลงลายเซ็นบนไบต์ที่เราส่งอย่างตรงตัว และไบต์เหล่านั้นคือ JSON serialization แบบมาตรฐาน (เรียงลำดับคีย์ ตัวคั่นแบบกระชับ) ดังนั้น การตรวจสอบกับเนื้อหาดิบจึงใช้ได้เสมอ — และหากเฟรมเวิร์กของคุณ ส่งให้เฉพาะ JSON ที่แยกวิเคราะห์แล้ว การ serialize ใหม่โดยเรียงลำดับคีย์ และใช้ตัวคั่นแบบกระชับจะสร้างไบต์ที่เหมือนกัน ทั้งสองวิธีอธิบายไว้ใน คู่มือการตรวจสอบ

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
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);
  },
);

ความหมายด้านการส่ง

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

การลองใหม่

แต่ละอีเวนต์จะถูกพยายามส่งทันทีหนึ่งครั้ง การตอบกลับ 2xx ใดๆ จะยืนยันการส่ง สำหรับผลลัพธ์อื่นทั้งหมด (ไม่ใช่ 2xx ข้อผิดพลาดการเชื่อมต่อ หมดเวลา) เราจะลองใหม่ที่ 1 นาที 5 นาที 30 นาที 2 ชั่วโมง 6 ชั่วโมง 12 ชั่วโมง และ 24 ชั่วโมงหลังการพยายามครั้งแรก — รวม 8 ครั้งภายใน 24 ชั่วโมง หากทุกครั้งล้มเหลว การส่งจะหยุดลงและปลายทาง จะถูกทำเครื่องหมายเป็น status="failing" ใน ปลายทางเว็บฮุก ส่งกลับ 2xx ทันทีที่ รับ payload อย่างถาวรแล้ว และประมวลผลแบบอะซิงโครนัส

ลำดับ

ลำดับการส่งเป็นแบบพยายามอย่างดีที่สุด โดยทั่วไปเราจะส่งตามลำดับที่ อีเวนต์ถูกปล่อยออกมา แต่การลองใหม่อาจทำให้ลำดับเปลี่ยนเมื่อเกิดความล้มเหลว ให้ตรวจจับรายการซ้ำและกระทบยอดด้วย call_id / id ของออบเจ็กต์เสมอ

รายการซ้ำ

การส่งเป็นแบบ อย่างน้อยหนึ่งครั้ง: การลองใหม่หลังจากการตอบกลับที่เราไม่เคย เห็นอาจทำให้อีเวนต์ซ้ำ การลองใหม่ทุกครั้งมี event_id เดียวกัน ดังนั้นให้จัดเก็บ id ที่ประมวลผลแล้วและข้ามรายการซ้ำ event_id ยังใช้ร่วมกันระหว่างปลายทางด้วย — ปลายทางสองรายการที่สมัครรับ อีเวนต์เดียวกันจะได้รับ event_id เดียวกัน

การหมดเวลา

การส่งไปยังปลายทางมีเวลาหมดเวลา 30 วินาที ต่อการพยายามหนึ่งครั้ง บน เส้นทางเดิม คำขอแบบบล็อกที่ขับเคลื่อนพฤติกรรมการโทรสด — การแลกเปลี่ยน การกำหนดค่า telephony.incoming / web.incoming — จะหมดเวลาหลัง 10 วินาที แต่การตอบกลับที่ช้าจะทำให้การรับสายล่าช้า ดังนั้นควรตอบกลับภายในไม่กี่วินาที โหมดเว็บฮุกสำหรับ การเรียกใช้เครื่องมือ อนุญาต 20 วินาที

IP ต้นทาง

เว็บฮุกขาออกมาจากช่วง IP คลาวด์ของ ThunderPhone หากไฟร์วอลล์ของคุณต้องการรายการที่อนุญาต โปรดติดต่อฝ่ายสนับสนุน แล้วเราจะ แจ้งช่วง IP ปัจจุบันให้

การเลือกระหว่างเว็บฮุกแบบเดิมและแบบใช้ปลายทาง

คุณสมบัติแบบเดิม (/v1/webhook)ปลายทาง (/v1/developer/webhook-endpoints)
จำนวน URL1 รายการต่อองค์กรหลายรายการต่อองค์กร
ขอบเขตอีเวนต์เฉพาะ telephony.* / web.*อีเวนต์ทั้ง 10 ประเภท
ตัวกรองอีเวนต์แยกตามปลายทาง
การลองใหม่ไม่มี8 ครั้งภายใน 24 ชั่วโมง
ซองข้อมูลtype + datatype + data + event_id
การหมุนเวียนซีเคร็ตแทนที่ซีเคร็ตเดี่ยวซีเคร็ตแยกตามปลายทาง
ปิดใช้งานโดยไม่ลบstatus=disabled
การมองเห็นสถานะactive / disabled / failing
การแลกเปลี่ยนการกำหนดค่าแบบบล็อกมี (telephony.incoming / web.incoming)ไม่มี — สำหรับการแจ้งเตือนเท่านั้น
เหมาะสำหรับการกำหนดค่าการโทรแบบไดนามิกการรับอีเวนต์ในระบบใช้งานจริง

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


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