Ikhtisar Webhook
Cara ThunderPhone mengirimkan peristiwa secara real-time, cara memverifikasi tanda tangan, serta perbandingan model pengiriman lama dan berbasis endpoint.
ThunderPhone mengirim permintaan HTTP POST ke server Anda saat peristiwa
terjadi selama panggilan — panggilan masuk dimulai, panggilan berakhir, proses
penilaian selesai, peringatan dipicu, dan sebagainya. Ada dua model
pengiriman:
Beberapa URL, rahasia per endpoint, filter peristiwa per endpoint,
dan percobaan ulang otomatis.
Kelola melalui GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Satu URL per organisasi. Memuat peristiwa siklus hidup panggilan, termasuk
pertukaran konfigurasi yang memblokir. Dikelola melalui GET/PUT /v1/webhook.
Kesepuluh jenis peristiwa dalam katalog peristiwa
dikirim melalui endpoint webhook. Enam peristiwa siklus hidup panggilan
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) juga dikirim ke webhook
lama dengan satu URL — jika Anda memiliki URL lama dan endpoint yang
cocok, Anda menerima peristiwa melalui kedua jalur. Perilaku pemblokiran
(pertukaran konfigurasi telephony.incoming / web.incoming
dan pengiriman tool mode webhook)
hanya tersedia pada jalur lama; setiap pengiriman endpoint adalah
notifikasi tanpa menunggu respons.
Format payload
Pengiriman endpoint berupa objek JSON dengan data, event_id, dan
type:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}event_id bersifat unik untuk setiap peristiwa yang dikirim. Nilainya identik di seluruh percobaan ulang
dan di setiap endpoint yang menerima peristiwa — lakukan deduplikasi berdasarkan nilai tersebut.
Webhook lama dengan satu URL mengirim type dan data yang sama, tetapi
tanpa event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}Dalam transmisi, setiap body diserialisasi secara kanonis — kunci diurutkan secara alfabetis, tanpa spasi kosong, UTF-8. Contoh yang diformat rapi dalam dokumentasi ini hanya untuk keterbacaan.
Lihat Katalog peristiwa untuk daftar lengkap jenis peristiwa dan field payload.
Verifikasi tanda tangan
Setiap permintaan membawa tanda tangan HMAC-SHA256 atas isi permintaan
mentah dalam header X-ThunderPhone-Signature. Kunci penandatanganan adalah
secret endpoint (atau secret webhook tingkat organisasi Anda untuk
pengiriman lama).
Langkah
- Baca isi permintaan mentah sebelum pemrosesan apa pun.
- Hitung
hmac_sha256(secret, body).hexdigest(). - Bandingkan dalam waktu konstan dengan header
X-ThunderPhone-Signature.
Kami menandatangani tepat byte yang kami kirimkan, dan byte tersebut adalah serialisasi JSON kanonis (kunci diurutkan, pemisah ringkas). Jadi, verifikasi terhadap isi mentah selalu berfungsi — dan jika framework Anda hanya memberikan JSON yang telah diurai, menserialisasikannya kembali dengan kunci yang diurutkan dan pemisah ringkas akan menghasilkan byte yang identik. Kedua metode dibahas dalam panduan verifikasi.
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);
},
);Semantik pengiriman
Semantik ini berlaku untuk pengiriman ke endpoint. Webhook URL tunggal legacy adalah satu upaya sinkron tanpa percobaan ulang.
Percobaan ulang
Setiap event dicoba sekali secara langsung. Respons 2xx apa pun
mengonfirmasi pengiriman. Pada hasil lainnya (non-2xx, kesalahan
koneksi, waktu habis), kami mencoba ulang pada 1 menit, 5 menit, 30 menit, 2 jam, 6 jam,
12 jam, dan 24 jam setelah upaya pertama — 8 upaya dalam rentang
24 jam. Jika semua upaya gagal, pengiriman berhenti dan endpoint
ditandai dengan status="failing" di
endpoint webhook. Kembalikan 2xx segera
setelah payload dipastikan tersimpan dengan andal; proses secara asinkron.
Urutan
Urutan pengiriman bersifat best-effort. Dalam praktiknya, kami mengirimkan sesuai
urutan event dipancarkan, tetapi percobaan ulang dapat mengubah urutan saat terjadi kegagalan.
Selalu lakukan deduplikasi dan rekonsiliasi berdasarkan call_id / id objek.
Duplikat
Pengiriman bersifat at-least-once: percobaan ulang setelah respons yang tidak
kami terima dapat menduplikasi event. Setiap percobaan ulang membawa
event_id yang sama, jadi simpan id yang telah diproses dan lewati pengulangan. event_id juga
digunakan bersama di seluruh endpoint — dua endpoint yang berlangganan pada
event yang sama menerima event_id yang sama.
Waktu habis
Pengiriman endpoint memiliki waktu habis 30 dtk per upaya. Pada
jalur legacy, permintaan yang memblokir dan mengendalikan perilaku panggilan langsung — pertukaran
konfigurasi telephony.incoming / web.incoming —
akan habis waktu setelah 10 dtk, tetapi respons yang lambat menunda
panggilan dijawab, jadi usahakan menjawab dalam beberapa
detik. Dispatch tool mode webhook mengizinkan 20 dtk
secara default, dan deklarasi tool dapat menetapkan timeout tingkat atas.
IP sumber
Webhook keluar berasal dari rentang IP cloud ThunderPhone. Jika firewall Anda memerlukan daftar izin, hubungi dukungan dan kami akan membagikan rentang terkini.
Memilih antara webhook legacy dan berbasis endpoint
| Fitur | Legacy (/v1/webhook) | Endpoint (/v1/developer/webhook-endpoints) |
|---|---|---|
| Jumlah URL | 1 per organisasi | Banyak per organisasi |
| Cakupan event | Hanya telephony.* / web.* | Semua 10 jenis event |
| Filter event | — | Per endpoint |
| Percobaan ulang | Tidak ada | 8 upaya selama 24 jam |
| Envelope | type + data | type + data + event_id |
| Rotasi secret | Mengganti satu secret | Secret per endpoint |
| Nonaktifkan tanpa menghapus | — | status=disabled |
| Visibilitas status | — | active / disabled / failing |
| Pertukaran konfigurasi yang memblokir | Ya (telephony.incoming / web.incoming) | Tidak pernah — hanya notifikasi |
| Paling cocok untuk | Konfigurasi panggilan dinamis | Konsumsi event di produksi |
Integrasi baru sebaiknya mengonsumsi event melalui webhook berbasis endpoint. Pertahankan (atau tambahkan) URL legacy hanya jika Anda mengonfigurasi panggilan secara dinamis saat panggilan dijawab atau menggunakan dispatch tool mode webhook — pertukaran permintaan/respons tersebut hanya berjalan pada jalur legacy.
Terkait
Semua jenis event dan payload-nya.
Kelola beberapa endpoint, filter event, dan secret.
Permintaan yang memblokir dan harus dijawab server Anda untuk mengonfigurasi panggilan.
Payload pascapanggilan dengan transkrip, rekaman, dan metrik.