---
title: "telephony.incoming / web.incoming"
description: "Blokkerende webhook die de configuratie van een inkomende oproep in realtime vormgeeft."
---

Wanneer een inkomend telefoongesprek een nummer bereikt **zonder
toegewezen spraakagent**, of wanneer een websidgetsessie start op een publiceerbare sleutel in
`mode="webhook"`, stuurt ThunderPhone een **blokkerend**
`telephony.incoming` / `web.incoming`-verzoek naar je
[verouderde webhook-URL](/api-reference/organizations#legacy-single-url-webhook)
en wacht op een configuratieantwoord: maximaal **13 seconden** voor een
telefoongesprek, inclusief één nieuwe poging, en maximaal **10 seconden** voor een widgetsessie
(zie [Time-outs en nieuwe pogingen](#timeouts-and-retries)). Gebruik deze
uitwisseling om per gesprek dynamisch een prompt, stem en tools te kiezen —
zie de [gids voor dynamische oproepconfiguratie](/nl/guides/dynamic-call-config)
voor het volledige patroon.

<Note>
  Geabonneerde [webhook-endpoints](/nl/webhooks/endpoints) ontvangen ook
  `telephony.incoming` / `web.incoming` — voor **elke** inkomende oproep
  en websessie, ongeacht of er een agent is geconfigureerd — maar deze
  leveringen zijn niet-blokkerende meldingen met een `event_id`.
  Alleen de verouderde webhook met één URL bevat de
  configuratie-uitwisseling op deze pagina. De vormen van endpointmeldingen
  staan in de [gebeurteniscatalogus](/nl/webhooks/events).
</Note>

## Time-outs en nieuwe pogingen

Reageer snel: de beller wacht aan de lijn terwijl je beslist.

Voor een telefoongesprek (`telephony.incoming`):

- De eerste poging kan maximaal **10 seconden** duren.
- Als deze een time-out krijgt, geen verbinding kan maken of een `5xx`-, `408`- of `429`-antwoord ontvangt, probeert ThunderPhone het nog één keer. De nieuwe poging krijgt wat er over is van een budget van **13 seconden** voor de volledige uitwisseling: ongeveer 3 seconden na een eerste poging met een time-out, bijna alle 13 seconden na een snelle fout. Er wordt niet opnieuw geprobeerd als er minder dan 2 seconden over zouden zijn.
- Elk ander `4xx`-antwoord, een antwoord boven de [groottelimiet](#response-size-limit), of een `2xx` waarvan de body geen geldige configuratie is, is definitief en wordt niet opnieuw geprobeerd.
- Beide pogingen verzenden dezelfde body, dezelfde header `X-ThunderPhone-Signature` en dezelfde header `X-ThunderPhone-Idempotency-Key` (`telephony-incoming-<call_id>`), zodat een handler met neveneffecten de nieuwe poging kan herkennen.

Er is geen fallback-spraakagent. Als er geen bruikbare configuratie binnenkomt, wordt de oproep direct beëindigd in plaats van de beller op een stille lijn te laten wachten. De oproep verschijnt in de Oproepgeschiedenis als een mislukte, niet-gefactureerde oproep met `end_reason` `inbound_config_webhook_failed`, en de gebruikelijke gebeurtenis [`telephony.complete`](/nl/webhooks/call-complete) wordt verzonden.

Een widgetsessie (`web.incoming`) krijgt één poging van maximaal 10 seconden. Als deze mislukt, mislukt het sessieverzoek met `502` of `422`.

<Warning>
  **Via webhook geconfigureerde oproepen bevatten geen
  toestemmingmelding van ThunderPhone.** Oproepen die via deze uitwisseling
  worden geconfigureerd, omzeilen de aankondiging bij de start van een oproep
  op agentniveau en zijn uitdrukkelijk uitgesloten van het kader voor
  toestemmingmeldingen van ThunderPhone (Servicevoorwaarden,
  sectie "Opname en toestemming"). Je organisatie is volledig
  verantwoordelijk voor elke vereiste melding en toestemming voor opname,
  monitoring, AI-deelname en identificatie van de beller bij deze oproepen —
  de oproepen kunnen nog steeds worden opgenomen, getranscribeerd, geanalyseerd
  en door AI afgehandeld. Neem de vereiste kennisgevingen op in je eigen
  oproepstroom voordat je dit pad inschakelt.
</Warning>

## Verzoekpayload

Voor telefoongesprekken (`telephony.incoming`):

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

| Veld | Type | Beschrijving |
|-------|------|-------------|
| `call_id` | integer | Oproep-ID — stabiel voor alle gebeurtenissen van deze oproep |
| `from_number` | string | E.164-nummer van de beller |
| `to_number` | string | E.164-bestemming (een van je ThunderPhone-nummers) |

Voor webwidgetsessies (`web.incoming`) identificeert `data` de
insluitende pagina in plaats van telefoonnummers:

```json
{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
```

| Veld | Type | Beschrijving |
|-------|------|-------------|
| `call_id` | integer | Oproep-ID |
| `origin_domain` | string | De herkomstpagina waarop de widget wordt gehost, zoals de browser deze heeft verzonden. Een niet-browserclient kan elke waarde verzenden, dus beschouw dit niet als bewijs van waar het verzoek vandaan kwam |
| `publishable_key_prefix` | string | Eerste tekens van de publiceerbare sleutel waarmee de sessie is geopend |
| `language`, `primary_language` | string | Aanwezig wanneer de widgetsessie een taaloverschrijving heeft aangevraagd |
| `voice` | string | Aanwezig wanneer de widgetsessie een stemoverschrijving heeft aangevraagd |
| `website_context` | string | Aanwezig wanneer de widget paginacontext per sessie heeft doorgegeven |

De overschrijvingsvelden (`language`, `voice`, `website_context` en eventuele
browser-`variables`) worden alleen verzonden wanneer de publiceerbare sleutel deze toestaat in
de bijbehorende [browseroverschrijvingen](/nl/guides/embed-web-widget). Een
sessieverzoek met een overschrijving die de sleutel niet toestaat, wordt afgewezen voordat
deze webhook wordt aangeroepen.

<Note>
  Widgets in webhookmodus leveren dit verzoek af bij de eigen
  `webhook_url` van de publiceerbare sleutel wanneer die is ingesteld, en vallen
  anders terug op de webhook-URL op organisatieniveau. In beide gevallen wordt
  het ondertekend met de webhook-`secret` van de organisatie.
</Note>

---

## Antwoordschema

Geef een JSON-object terug dat de configuratie van de spraakagent voor dit gesprek beschrijft.
`prompt` en `voice` zijn vereist; al het andere is optioneel.

```json
{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
```

| Veld | Type | Vereist | Beschrijving |
|-------|------|----------|-------------|
| `prompt` | string | ja | Systeemprompt die de spraakagent aanstuurt |
| `voice` | string | ja | Stem-ID van [`GET /v1/voices`](/api-reference/agents#voices), bijvoorbeeld `john`. `voice_name` wordt geaccepteerd als alias. Onbekende stemmen mislukken bij validatie en weigeren de oproep |
| `product` | string | nee | Standaard ingesteld op `spark`. Toegestaan: `spark`, `bolt`, `storm-base`, `storm-base-with-ack`, `storm-extra`, `storm-extra-with-ack` |
| `thinking_level` | string | nee | `minimal`, `base` (standaard) of `extra`. Overschreven voor Storm-producten: `storm-extra*` dwingt `extra` af, andere `storm-*` dwingen `base` af |
| `audio_context_mode` | string | nee | `full` (standaard) of `reduced` |
| `watchdog_enabled` | boolean | nee | Schakel toezicht in voor deze oproep. Standaard `false` |
| `additional_audio_context` | boolean \| null | nee | Neem de laatste paar beurten van de audio van de beller op in plaats van alleen de meest recente beurt. Dit verbetert correcties en het verzamelen van gegevens met veel spelling of nummers, met een kleine extra vertraging en kosten. Standaard ingeschakeld voor inkomende sessies en uitgeschakeld voor uitgaande telefoongesprekken; `null` behoudt de standaardinstelling |
| `storm_feedback_mode` | string | nee | `none`, `acknowledgement` (standaard) of `tick` |
| `language` | string | nee | Verkorte vorm van `primary_language` |
| `primary_language` | string | nee | Taalcode, genormaliseerd (standaard `en`). Codes die niet kunnen worden opgelost, weigeren de oproep |
| `has_additional_languages` | boolean | nee | Standaard `false` |
| `additional_languages` | array van string | nee | Extra talen waarnaar de spraakagent kan overschakelen |
| `native_voice_switching` | boolean | nee | Standaard `false`. Wanneer de oproep naar een andere taal overschakelt, wissel je naar een stem die moedertaalspreker is van die taal (afgestemd op gender) in plaats van de geconfigureerde stem te behouden |
| `language_switching` | string \| null | nee | `ask_first` (standaard) of `follow_caller`; `null` is hetzelfde als `ask_first`. Bepaalt hoe een meertalige spraakagent omgaat met de andere persoon die een van zijn andere talen spreekt. `ask_first` schakelt direct over wanneer deze persoon om de taal vraagt of er een volledige zin in zegt, en vraagt na iets korts, zoals „sí”, of er in die taal moet worden doorgegaan. `follow_caller` schakelt over naar elke geconfigureerde taal die deze persoon spreekt, zelfs bij één woord. Geldt alleen wanneer `additional_languages` is ingesteld |
| `background_track` | string \| null | nee | ID van omgevingsaudio of `null` |
| `acknowledgement_prompt_mode` | string | nee | `auto` (standaard) of `manual` (Storm-with-ack-producten) |
| `acknowledgement_prompt` | string | nee | Gebruikt wanneer `acknowledgement_prompt_mode="manual"` |
| `silence_interval_seconds` | integer \| null | nee | 5–120. Seconden stilte van de beller vóór een controle |
| `silence_max_checkins` | integer \| null | nee | 1–10 |
| `silence_checkins_enabled` | boolean | nee | Standaard `true` |
| `connect_tone_enabled` | boolean | nee | Standaard `false` |
| `pronunciation_dicts` | boolean \| null | nee | Standaard `false`; `null` is hetzelfde als `false`. Wanneer `true`, gebruikt de Cartesia-spraak van de oproep het uitspraakwoordenboek van ThunderPhone voor de taal van de oproep, als er een bestaat. Werkt voor telefoon- en widgetoproepen. Geldt alleen voor de eigen Cartesia-stemmen van ThunderPhone, niet voor stemmen met je eigen Cartesia-API-sleutel. Niet gebruikt met `agent_id`, waarbij de instellingen van de opgeslagen spraakagent gelden |
| `voicemail_action` | string | nee | `prompt` (standaard), `hangup` of `message` |
| `voicemail_message` | string | nee | Gebruikt wanneer `voicemail_action="message"` |
| `agent_name` | string | nee | Weergavenaam die wordt doorgegeven aan dashboards en de widget |
| `org_name` | string | nee | Weergavenaam van de organisatie voor de persona van de spraakagent |
| `tools` | array | nee | Inline schema's voor functiehulpmiddelen (zie [Functiehulpmiddelen](/nl/tools/overview)) |
| `transfer_destinations` | array van object \| null | nee | Maximaal 50 items `{name, number}` waarnaar deze oproep kan worden doorverbonden, gevalideerd zoals de [doorschakelnummers](/api-reference/agents#transfer-numbers) van een spraakagent. De spraakagent verbindt door op naam en alleen naar deze items; `[]` betekent geen doorschakelingen. Laat dit weg of stuur `null` om de spraakagent naar een nummer naar keuze te laten doorverbinden |
| `timezone` | string | nee | IANA-tijdzone voor deze oproep, zoals `America/Chicago`. De spraakagent vermeldt de datum en tijd daarin, in plaats van die in de eigen tijdzone van de spraakagent. Ook toegestaan naast `agent_id`. Waarden die geen IANA-tijdzones zijn, worden genegeerd. Zie [De tijdzone per oproep instellen](/nl/guides/dynamic-call-config) |
| `call_id` | integer | nee | Optionele echo van de oproep-ID van het verzoek; genegeerd |

<Note>
  Onbekende sleutels op het hoogste niveau worden stilzwijgend **genegeerd** — een veldnaam
  met een typefout wijst de configuratie niet af, maar wordt gewoon niet toegepast. Spreekvolgorde
  en `max_hold_seconds` worden hier niet geaccepteerd; ze kunnen alleen
  op de [spraakagent](/api-reference/agents) zelf worden geconfigureerd.
</Note>

Omdat `prompt` en `voice` vereist zijn, weigert het retourneren van `{}` of een andere respons die de validatie niet doorstaat de oproep: een telefoongesprek wordt beëindigd zoals beschreven in [Time-outs en nieuwe pogingen](#timeouts-and-retries), en een widgetsessieverzoek mislukt met `422`. Er is geen terugval op een statische agent via dit pad (een nummer of sleutel in webhookmodus heeft geen toegewezen agent).

---

## Limiet voor responsgrootte

<Warning>
  Configuratieresponsen zijn beperkt tot **5 MiB**. Als een handler
  een grotere respons retourneert, ook met een `2xx`-status,
  meldt ThunderPhone dat de respons de limiet heeft overschreden en
  weigert het de oproep- of widgetsessie. Beperk de respons tot de velden
  die nodig zijn voor het instellen van de oproep; host grote gegevens achter
  functietools of een andere service in plaats van ze in de configuratie in te sluiten.
</Warning>

---

## Voorbeeldhandler

<CodeGroup>
```python Python (FastAPI)
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]

def verify(body: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

@app.post("/thunderphone-webhook")
async def webhook(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(status_code=401)

    event = json.loads(body)
    if event["type"] == "telephony.incoming":
        caller = event["data"]["from_number"]
        prompt = (
            "Greet the caller as a San Francisco local…"
            if caller.startswith("+1415")
            else "You are a friendly customer support agent…"
        )
        return {
            "prompt": prompt,
            "voice": "john",
            "product": "spark",
        }
    if event["type"] == "web.incoming":
        return {
            "prompt": "You are the website's helpful voice assistant…",
            "voice": "john",
            "product": "spark",
        }
    return {}
```

```javascript Node.js (Express)
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;

function verify(body, signature) {
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(body)
    .digest("hex");
  return signature &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));

    if (event.type === "telephony.incoming" || event.type === "web.incoming") {
      const caller = event.data.from_number || "web";
      const prompt = caller.startsWith("+1415")
        ? "Greet the caller as a San Francisco local…"
        : "You are a friendly customer support agent…";
      return res.json({
        prompt,
        voice: "john",
        product: "spark",
      });
    }
    res.json({});
  },
);
```
</CodeGroup>

---

## Respons met functietools

Koppel tools zodat de AI tijdens het gesprek je API's kan aanroepen:

```json
{
  "prompt":  "You are a booking assistant. Use the available tools to help customers schedule appointments.",
  "voice":   "john",
  "product": "spark",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": {
          "X-Api-Key": "your-key"
        }
      }
    }
  ]
}
```

<Tip>
  Verzoeken naar tool-eindpunten worden ondertekend met hetzelfde **organisatie-webhookgeheim**
  dat deze uitwisseling heeft ondertekend. Zie
  [Functietools](/nl/tools/overview) voor de exacte structuur en de indeling van het
  ondertekende verzoek.
</Tip>

---

## Spiekbriefje productpakketten

| Product | Latentie | Redeneren | Bevestiging |
|---------|---------|-----------|-----------------|
| `spark` | Laagst | Basis | — |
| `bolt` | Laag | Verbeterd | — |
| `storm-base` | Gemiddeld | Sterk | — |
| `storm-base-with-ack` | Gemiddeld | Sterk | Automatische opvulling tijdens het nadenken |
| `storm-extra` | Hoger | Diepgaand | — |
| `storm-extra-with-ack` | Hoger | Diepgaand | Automatische opvulling tijdens het nadenken |

---

## Gerelateerd

<CardGroup cols={2}>
  <Card title="telephony.complete / web.complete" icon="phone-slash" href="/nl/webhooks/call-complete">
    De niet-blokkerende gebeurtenis aan het einde van een oproep.
  </Card>
  <Card title="Function Tools" icon="screwdriver-wrench" href="/nl/tools/overview">
    Volledig JSON-schema voor `tools[]` en het ondertekende endpointcontract.
  </Card>
  <Card title="Webhook-endpoints" icon="bolt" href="/nl/webhooks/endpoints">
    Abonneer meerdere URL's op `telephony.incoming` / `web.incoming`.
  </Card>
  <Card title="Dynamische oproepconfiguratie" icon="wand-magic-sparkles" href="/nl/guides/dynamic-call-config">
    Patronen voor prompts, tools en A/B-tests per beller.
  </Card>
</CardGroup>
