ThunderPhone 2.0 kini resmi hadir.Layanan mandiri, mulai dari 2¢/menit.Baca pengumumannya

Webhooks

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:

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

  1. Baca isi permintaan mentah sebelum pemrosesan apa pun.
  2. Hitung hmac_sha256(secret, body).hexdigest().
  3. 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.

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

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

FiturLegacy (/v1/webhook)Endpoint (/v1/developer/webhook-endpoints)
Jumlah URL1 per organisasiBanyak per organisasi
Cakupan eventHanya telephony.* / web.*Semua 10 jenis event
Filter eventPer endpoint
Percobaan ulangTidak ada8 upaya selama 24 jam
Envelopetype + datatype + data + event_id
Rotasi secretMengganti satu secretSecret per endpoint
Nonaktifkan tanpa menghapusstatus=disabled
Visibilitas statusactive / disabled / failing
Pertukaran konfigurasi yang memblokirYa (telephony.incoming / web.incoming)Tidak pernah — hanya notifikasi
Paling cocok untukKonfigurasi panggilan dinamisKonsumsi 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