> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 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 ketika sesuatu
terjadi selama panggilan — panggilan masuk dimulai, panggilan berakhir, proses
penilaian selesai, peringatan dipicu, dan sebagainya. Ada **dua model
pengiriman**:

<CardGroup cols={2}>
  <Card title="Endpoint webhook (direkomendasikan)" icon="bolt" href="/id/webhooks/endpoints">
    Beberapa URL, secret per endpoint, filter peristiwa per endpoint,
    dan percobaan ulang otomatis.
    Kelola melalui `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>

  <Card title="Webhook legacy satu URL" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Satu URL per organisasi. Membawa peristiwa siklus hidup panggilan, termasuk
    pertukaran konfigurasi **yang memblokir**. Dikelola di `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Kesepuluh jenis peristiwa dalam [katalog peristiwa](/id/webhooks/events)
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
legacy satu URL — jika Anda memiliki URL legacy dan endpoint yang cocok, Anda
menerima peristiwa pada **kedua** jalur. Perilaku pemblokiran ( [pertukaran
konfigurasi `telephony.incoming` / `web.incoming`](/id/webhooks/call-incoming)
dan [pengiriman tool](/id/tools/overview) mode webhook)
hanya tersedia pada jalur legacy; setiap pengiriman endpoint adalah notifikasi
fire-and-forget.

## Format payload

Pengiriman endpoint berupa objek JSON dengan `data`, `event_id`, dan
`type`:

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

`event_id` unik untuk setiap peristiwa yang dipancarkan. Nilainya identik di seluruh percobaan ulang
**dan** di setiap endpoint yang menerima peristiwa — lakukan deduplikasi berdasarkan nilai tersebut.

Webhook legacy satu URL mengirim `type` dan `data` yang sama, tetapi
**tanpa** `event_id`:

```json theme={null}
{
  "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, UTF-8. Contoh yang dicetak rapi dalam
dokumentasi ini hanya untuk keterbacaan.

Lihat [Katalog peristiwa](/id/webhooks/events) untuk daftar lengkap jenis
peristiwa dan field payload.

## Verifikasi tanda tangan

Setiap permintaan membawa tanda tangan HMAC-SHA256 atas **body permintaan
mentah** dalam header `X-ThunderPhone-Signature`. Kunci penandatanganan adalah
`secret` endpoint (atau `secret` webhook tingkat organisasi Anda untuk
pengiriman lama).

### Langkah

1. Baca body permintaan mentah **sebelum** parsing apa pun.
2. Hitung `hmac_sha256(secret, body).hexdigest()`.
3. Bandingkan dalam waktu konstan dengan header `X-ThunderPhone-Signature`.

Kami menandatangani byte yang tepat kami kirimkan, dan byte tersebut adalah
serialisasi JSON kanonis (kunci diurutkan, pemisah ringkas). Jadi,
memverifikasi terhadap body mentah selalu berfungsi — dan jika framework Anda
hanya memberikan JSON yang telah diurai, menserialisasikannya kembali dengan
kunci yang diurutkan dan pemisah ringkas menghasilkan byte yang identik. Kedua
metode dibahas dalam [panduan verifikasi](/id/guides/verify-webhook-signatures).

<CodeGroup>
  ```python Python theme={null}
  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) theme={null}
  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>

## Semantik pengiriman

Semantik ini berlaku untuk pengiriman **endpoint**. Webhook URL tunggal
legacy adalah satu percobaan sinkron tanpa percobaan ulang.

<AccordionGroup>
  <Accordion title="Percobaan ulang">
    Setiap event langsung dicoba sekali. Respons `2xx` apa pun
    mengakui pengiriman. Untuk hasil lainnya (non-2xx,
    kesalahan koneksi, waktu habis), kami mencoba ulang pada **1 m, 5 m, 30 m, 2 h, 6 h,
    12 h, dan 24 h setelah percobaan pertama** — 8 percobaan selama
    24 jam. Jika setiap percobaan gagal, pengiriman berhenti dan endpoint
    ditandai sebagai `status="failing"` di
    [endpoint webhook](/id/webhooks/endpoints). Kembalikan `2xx` segera setelah
    payload diterima dan disimpan secara andal; proses secara asinkron.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="Duplikat">
    Pengiriman bersifat **at-least-once**: percobaan ulang setelah respons yang tidak pernah
    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
    dibagikan di seluruh endpoint — dua endpoint yang berlangganan pada
    event yang sama menerima `event_id` yang sama.
  </Accordion>

  <Accordion title="Waktu habis">
    Pengiriman endpoint memiliki waktu habis **30 s** per percobaan. Pada
    jalur legacy, permintaan pemblokiran yang mengatur perilaku panggilan langsung —
    pertukaran konfigurasi
    [`telephony.incoming` / `web.incoming`](/id/webhooks/call-incoming) — akan
    mengalami waktu habis setelah **10 s**, tetapi respons yang lambat menunda
    penerimaan panggilan, jadi usahakan menjawab dalam beberapa
    detik. [Dispatch tool](/id/tools/overview) mode webhook memungkinkan 20 s.
  </Accordion>

  <Accordion title="IP sumber">
    Webhook keluar berasal dari rentang IP cloud ThunderPhone.
    Jika firewall Anda memerlukan allowlist, hubungi dukungan dan kami akan
    membagikan rentang saat ini.
  </Accordion>
</AccordionGroup>

## 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 percobaan selama 24 h                      |
| 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`](/id/webhooks/call-incoming)) | Tidak pernah — hanya notifikasi              |
| Paling sesuai untuk                   | Konfigurasi panggilan dinamis                                            | Pemrosesan event di produksi                 |

Integrasi baru harus menerima event melalui webhook berbasis endpoint.
Pertahankan (atau tambahkan) URL legacy hanya jika Anda mengonfigurasi panggilan
secara dinamis saat panggilan diangkat atau menggunakan dispatch tool mode webhook — pertukaran
permintaan/respons tersebut hanya berjalan pada jalur legacy.

***

## Terkait

<CardGroup cols={2}>
  <Card title="Katalog event" icon="list" href="/id/webhooks/events">
    Semua jenis event dan payload-nya.
  </Card>

  <Card title="Endpoint webhook" icon="bolt" href="/id/webhooks/endpoints">
    Kelola beberapa endpoint, filter event, dan secret.
  </Card>

  <Card title="telephony.incoming / web.incoming" icon="phone" href="/id/webhooks/call-incoming">
    Permintaan pemblokiran yang harus dijawab server Anda untuk mengonfigurasi panggilan.
  </Card>

  <Card title="telephony.complete / web.complete" icon="phone" href="/id/webhooks/call-complete">
    Payload pascapanggilan dengan transkrip, rekaman, dan metrik.
  </Card>
</CardGroup>
