> ## 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.

# Overzicht van webhooks

> Hoe ThunderPhone realtime gebeurtenissen levert, hoe je handtekeningen verifieert en hoe de verouderde en endpointgebaseerde leveringsmodellen zich tot elkaar verhouden.

ThunderPhone verstuurt HTTP-`POST`-verzoeken naar je server wanneer er tijdens een oproep iets
gebeurt — een inkomende oproep begint, een oproep eindigt, een beoordelingsrun
wordt voltooid, een waarschuwing wordt geactiveerd, enzovoort. Er zijn **twee
bezorgmodellen**:

<CardGroup cols={2}>
  <Card title="Webhook-eindpunten (aanbevolen)" icon="bolt" href="/nl/webhooks/endpoints">
    Meerdere URL's, geheimen per eindpunt, gebeurtenisfilters per eindpunt
    en automatische herhalingspogingen.
    Beheer via `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>

  <Card title="Verouderde webhook met één URL" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Eén URL per organisatie. Bevat de gebeurtenissen in de oproepcyclus, inclusief de
    **blokkerende** configuratie-uitwisselingen. Beheer via `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Alle tien gebeurtenistypen in de [gebeurteniscatalogus](/nl/webhooks/events) worden
via webhook-eindpunten bezorgd. De zes gebeurtenissen in de oproepcyclus
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) worden **ook** naar de
verouderde webhook met één URL verzonden — als je zowel een verouderde URL als
een overeenkomend eindpunt hebt, ontvang je de gebeurtenis via **beide**
paden. Blokkerend gedrag (de [`telephony.incoming` / `web.incoming`-configuratie-
uitwisseling](/nl/webhooks/call-incoming) en verzending van tools in
[webhookmodus](/nl/tools/overview)) bestaat uitsluitend op
het verouderde pad; elke bezorging aan een eindpunt is een fire-and-forgetmelding.

## Payloadformaat

Bezorgingen aan eindpunten zijn een JSON-object met `data`, `event_id` en
`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` is uniek voor elke verzonden gebeurtenis. Het is identiek bij
herhalingspogingen **en** bij elk eindpunt dat de gebeurtenis ontvangt — gebruik
het voor deduplicatie.

De verouderde webhook met één URL verzendt hetzelfde `type` en dezelfde `data`, maar
**zonder** `event_id`:

```json theme={null}
{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
```

Tijdens verzending wordt elke body canoniek geserialiseerd — sleutels alfabetisch
gesorteerd, zonder witruimte, UTF-8. De mooi opgemaakte voorbeelden in
deze documentatie zijn uitsluitend voor leesbaarheid.

Bekijk de [gebeurteniscatalogus](/nl/webhooks/events) voor de volledige lijst met gebeurtenis-
typen en payloadvelden.

## Handtekeningverificatie

Elk verzoek bevat een HMAC-SHA256-handtekening van de **onbewerkte requestbody** in de header `X-ThunderPhone-Signature`. De ondertekeningssleutel is de `secret` van het eindpunt (of je webhook-`secret` op organisatieniveau voor verouderde leveringen).

### Stappen

1. Lees de onbewerkte requestbody **vóór** enige parsing.
2. Bereken `hmac_sha256(secret, body).hexdigest()`.
3. Vergelijk deze in constante tijd met de header `X-ThunderPhone-Signature`.

We ondertekenen exact de bytes die we verzenden, en die bytes zijn de canonieke JSON-serialisatie (gesorteerde sleutels, compacte scheidingstekens). Verificatie aan de hand van de onbewerkte body werkt dus altijd — en als je framework je alleen geparste JSON geeft, produceert het opnieuw serialiseren met gesorteerde sleutels en compacte scheidingstekens identieke bytes. Beide methoden worden behandeld in de [verificatiehandleiding](/nl/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>

## Afleveringssemantiek

Deze semantiek is van toepassing op leveringen via **eindpunten**. De verouderde webhook met één URL
is één synchrone poging zonder nieuwe pogingen.

<AccordionGroup>
  <Accordion title="Nieuwe pogingen">
    Elke gebeurtenis wordt onmiddellijk één keer geprobeerd. Elke `2xx`-reactie
    bevestigt de aflevering. Bij elke andere uitkomst (niet-2xx,
    verbindingsfout, time-out) proberen we opnieuw na **1 m, 5 m, 30 m, 2 h, 6 h,
    12 h en 24 h na de eerste poging** — 8 pogingen verspreid over
    24 uur. Als elke poging mislukt, stopt de aflevering en wordt het eindpunt
    gemarkeerd met `status="failing"` in
    [webhookeindpunten](/nl/webhooks/endpoints). Retourneer `2xx` zodra
    de payload duurzaam is geaccepteerd; verwerk asynchroon.
  </Accordion>

  <Accordion title="Volgorde">
    De aflevervolgorde gebeurt naar beste vermogen. In de praktijk leveren we in de
    volgorde waarin gebeurtenissen worden verzonden, maar nieuwe pogingen kunnen de volgorde wijzigen bij fouten.
    Dedupliceer en reconcilieer altijd op basis van `call_id` / object-ID.
  </Accordion>

  <Accordion title="Duplicaten">
    Aflevering is **minstens één keer**: een nieuwe poging na een reactie die we nooit
    hebben gezien, kan een gebeurtenis dupliceren. Elke nieuwe poging bevat dezelfde
    `event_id`, dus sla verwerkte ID's op en sla herhalingen over. `event_id` wordt
    ook gedeeld tussen eindpunten — twee eindpunten die zijn geabonneerd op dezelfde gebeurtenis
    ontvangen dezelfde `event_id`.
  </Accordion>

  <Accordion title="Time-outs">
    Leveringen via eindpunten hebben een time-out van **30 s** per poging. Op het
    verouderde pad hebben blokkerende verzoeken die live oproepgedrag bepalen — de
    configuratie-uitwisseling [`telephony.incoming` / `web.incoming`](/nl/webhooks/call-incoming) —
    een time-out na **10 s**, maar een trage reactie vertraagt het opnemen van de oproep,
    dus streef ernaar binnen enkele seconden te antwoorden. Tool-dispatch in webhookmodus
    [tool dispatch](/nl/tools/overview) staat 20 s toe.
  </Accordion>

  <Accordion title="Bron-IP's">
    Uitgaande webhooks zijn afkomstig uit het cloud-IP-bereik van ThunderPhone.
    Als je firewall een allowlist vereist, neem dan contact op met support en we
    delen de huidige bereiken.
  </Accordion>
</AccordionGroup>

## Kiezen tussen verouderde en op eindpunten gebaseerde webhooks

| Functie                               | Verouderd (`/v1/webhook`)                                                | Eindpunten (`/v1/developer/webhook-endpoints`) |
| ------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------- |
| Aantal URL's                          | 1 per organisatie                                                        | Meerdere per organisatie                       |
| Gebeurtenisdekking                    | Alleen `telephony.*` / `web.*`                                           | Alle 10 gebeurtenistypen                       |
| Gebeurtenisfilter                     | —                                                                        | Per eindpunt                                   |
| Nieuwe pogingen                       | Geen                                                                     | 8 pogingen in 24 h                             |
| Envelop                               | `type` + `data`                                                          | `type` + `data` + `event_id`                   |
| Secretrotatie                         | Vervangt één secret                                                      | Secret per eindpunt                            |
| Uitschakelen zonder verwijderen       | —                                                                        | `status=disabled`                              |
| Statuszichtbaarheid                   | —                                                                        | `active` / `disabled` / `failing`              |
| Blokkerende configuratie-uitwisseling | Ja ([`telephony.incoming` / `web.incoming`](/nl/webhooks/call-incoming)) | Nooit — alleen meldingen                       |
| Meest geschikt voor                   | Dynamische oproepconfiguratie                                            | Gebeurtenisverwerking in productie             |

Nieuwe integraties moeten gebeurtenissen verwerken via op eindpunten gebaseerde
webhooks. Behoud (of voeg) alleen een verouderde URL toe als je oproepen
dynamisch configureert bij het opnemen of tool-dispatch in webhookmodus gebruikt — die
verzoek-/reactie-uitwisselingen worden alleen uitgevoerd via het verouderde pad.

***

## Gerelateerd

<CardGroup cols={2}>
  <Card title="Gebeurteniscatalogus" icon="list" href="/nl/webhooks/events">
    Alle gebeurtenistypen en hun payloads.
  </Card>

  <Card title="Webhookeindpunten" icon="bolt" href="/nl/webhooks/endpoints">
    Beheer meerdere eindpunten, gebeurtenisfilters en secrets.
  </Card>

  <Card title="telephony.incoming / web.incoming" icon="phone" href="/nl/webhooks/call-incoming">
    Het blokkerende verzoek waarop je server moet antwoorden om oproepen te configureren.
  </Card>

  <Card title="telephony.complete / web.complete" icon="phone" href="/nl/webhooks/call-complete">
    Payload na de oproep met transcript, opname en statistieken.
  </Card>
</CardGroup>
