---
title: "Functietools"
description: "Geef je AI-agenten functietools die tijdens gesprekken externe API's aanroepen — klantgegevens ophalen, afspraken inplannen, records bijwerken — met getypeerde parameters."
---

Functietools stellen je AI-agenten in staat om tijdens telefoongesprekken externe API's aan te roepen. Gebruik ze om klantgegevens op te zoeken, beschikbaarheid te controleren, afspraken in te plannen of elke actie uit te voeren die je backend ondersteunt.

## Zo werkt het

1. Je definieert tools met een schema (welke argumenten de tool accepteert)
2. Je geeft een `endpoint`-configuratie op (waar ThunderPhone je API aanroept) — of laat deze weg om toolaanroepen op je organisatiewebhook te ontvangen
3. Tijdens een gesprek bepaalt de AI op basis van het gesprek wanneer een tool moet worden gebruikt
4. ThunderPhone roept je endpoint aan met de toolargumenten
5. Je API-respons wordt teruggekoppeld naar de AI om het gesprek voort te zetten

| Mogelijkheid | Waar het wordt uitgevoerd | Instellen |
| --- | --- | --- |
| [Ingebouwde tools](/nl/guides/built-in-tools) | ThunderPhone | Promptinstructies; sommige tools vereisen ook een agentinstelling |
| [App-koppelingen](/nl/guides/connect-apps) | ThunderPhone en de gekoppelde provider | Koppel het account en voeg goedgekeurde acties toe |
| [API-koppelingen](/nl/guides/api-connections) en functietools | Je HTTP API | Definieer het endpoint en schema, of ontvang functieaanroepen via een webhook |
| [MCP-servers](/nl/guides/mcp-servers) | Een externe MCP-server | Voeg de server toe, ontdek de tools en koppel deze aan de agent |

---

## Toolschema

Elke tool volgt deze structuur:

```json
{
  "type": "function",
  "function": {
    "name": "search_appointments",
    "description": "Find available appointment slots for a given date",
    "parameters": {
      "type": "object",
      "properties": {
        "date": {
          "type": "string",
          "description": "Date in YYYY-MM-DD format"
        },
        "service": {
          "type": "string",
          "description": "Type of service (e.g., 'consultation', 'follow-up')"
        }
      },
      "required": ["date"]
    }
  },
  "endpoint": {
    "url": "https://api.example.com/appointments/search",
    "method": "POST",
    "headers": {
      "X-Api-Key": "your-api-key"
    }
  },
  "timeout": 120
}
```

### Toolconfiguratie

| Veld | Type | Vereist | Beschrijving |
|-------|------|----------|-------------|
| `timeout` | nummer | Nee | Maximale uitvoeringstijd in seconden (standaard: `20`, maximum: `180`) |

### Functiedefinitie

| Veld | Type | Vereist | Beschrijving |
|-------|------|----------|-------------|
| `name` | tekenreeks | Ja | Unieke identificatie voor de tool |
| `description` | tekenreeks | Ja | Legt aan de AI uit wanneer deze tool moet worden gebruikt |
| `parameters` | object | Ja | JSON-schema voor toolargumenten |

### Endpointconfiguratie

| Veld | Type | Vereist | Beschrijving |
|-------|------|----------|-------------|
| `url` | tekenreeks | Ja | De URL van je API-endpoint |
| `method` | tekenreeks | Nee | HTTP-methode (standaard: `POST`) |
| `headers` | object | Nee | Aangepaste headers om op te nemen |

<Note>
  De `endpoint`-configuratie wordt **niet** naar het AI-model gestuurd — deze wordt alleen door ThunderPhone gebruikt om de toolaanroep uit te voeren.
</Note>

---

## Twee aanroeppaden

Welk verzoek je server ontvangt, hangt af van of de tool een
`endpoint` heeft:

| | Tool **met** `endpoint` | Tool **zonder** `endpoint` |
|---|---|---|
| Waar het verzoek naartoe gaat | Rechtstreeks naar `endpoint.url` | De [verouderde webhook-URL](/api-reference/organizations#legacy-single-url-webhook) van je organisatie |
| Hoofdtekst | Geselecteerde verzoekindeling (standaard alleen argumenten) | `telephony.tool` / `web.tool`-envelop |
| Headers | Je `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` |
| Ondertekeningssleutel | Webhookgeheim van de organisatie | Webhookgeheim van de organisatie |

Beide paden zijn **blokkerend** — de AI wacht midden in een zin op het
resultaat. De standaardtime-out is **20 s**; stel de `timeout` op het
hoogste niveau van de tool in om een langere uitvoering toe te staan, tot het
platformmaximum van **180 s**. Houd handlers snel. Een combinatie is prima:
bij een gesprek waarvan de organisatie een webhook-URL heeft, worden tools met een `endpoint`
rechtstreeks aangeroepen en vallen de overige terug op de webhook.

## Rechtstreekse endpointaanroepen

Wanneer de AI een tool met een `endpoint` aanroept, stuurt ThunderPhone
een verzoek naar je URL:

### Verzoekheaders

```http
POST /appointments/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature-V2: t=1767225600,v1=5f2c...
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key
```

Aangepaste headers uit je `endpoint.headers` worden altijd
letterlijk opgenomen, naast deze headers met de ThunderPhone-naamruimte:

- `X-ThunderPhone-Signature-V2` — HMAC-SHA256 met tijdstempel over de
  methode, het pad, de query en de body, met je **webhookgeheim van de organisatie** als sleutel.
  Verifieer deze en weiger verzoeken die ouder zijn dan 5 minuten; zie
  [Webhookhandtekeningen verifiëren](/nl/guides/verify-webhook-signatures)
- `X-ThunderPhone-Signature` — verouderde HMAC-SHA256 van de exacte
  bytes van de aanvraagbody, met dezelfde sleutel, nog steeds verzonden voor bestaande verificateurs
- `X-ThunderPhone-Call-ID` — De ID van de huidige oproep

`Content-Type: application/json` wordt ingesteld, tenzij je `endpoint.headers`
dit overschrijven — een aangepaste `Content-Type` heeft voorrang.

<Warning>
  De handtekening wordt versleuteld met het webhookgeheim op
  organisatieniveau uit
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook).
  Als je organisatie de verouderde webhook nooit heeft geconfigureerd,
  is er geen geheim en bevatten toolaanroepen **alleen**
  `X-ThunderPhone-Call-ID` — een handler die faalt bij een ontbrekende
  handtekening zou deze weigeren.
  Configureer de verouderde webhook om een geheim te verkrijgen, of plaats
  je eigen gedeelde geheim in `endpoint.headers`.
</Warning>

### Aanvraagindeling

Elke API-verbinding heeft een instelling `request_format`. Kies `vapi` of
`retell` wanneer een geïmporteerde tool een endpoint moet blijven aanroepen dat voor
dat platform is gebouwd. Nieuwe en bestaande verbindingen gebruiken standaard `standard`.

Voor `POST` / `PUT` / `PATCH` serialiseert ThunderPhone de geselecteerde
envelop canoniek (gesorteerde sleutels, compacte scheidingstekens). Beide handtekeningen
dekken deze exacte bytes.

#### Standard

`standard` verstuurt alleen de toolargumenten, zonder wrapper. Dit is de
standaardinstelling en behoudt de oorspronkelijke aanvraagbody van ThunderPhone byte voor byte:

```json
{"date":"2025-01-02","service":"consultation"}
```

#### Vapi

`vapi` verzendt het [`tool-calls`-serverbericht](https://docs.vapi.ai/tools/custom-tools) van Vapi
in de vorm die Vapi documenteert. De `call.id` is de oproep-ID van ThunderPhone. De
tool-call-`id` is uniek voor de aanroep en wordt hergebruikt om het
resultaat van het endpoint te koppelen. De oproep verschijnt in zowel `toolCallList` als
`toolWithToolCallList`, met `function.arguments` als JSON-object:

```json
{
  "message": {
    "type": "tool-calls",
    "timestamp": 1725000000123,
    "call": { "id": "987654321" },
    "toolCallList": [
      {
        "id": "call_00112233445566778899aabbccddeeff",
        "type": "function",
        "function": {
          "name": "search_appointments",
          "arguments": { "date": "2025-01-02", "service": "consultation" }
        }
      }
    ],
    "toolWithToolCallList": [
      {
        "type": "function",
        "function": { "name": "search_appointments" },
        "toolCall": {
          "id": "call_00112233445566778899aabbccddeeff",
          "type": "function",
          "function": {
            "name": "search_appointments",
            "arguments": { "date": "2025-01-02", "service": "consultation" }
          }
        }
      }
    ]
  }
}
```

Vapi-specifieke velden zoals `assistant`, `artifact` en het volledige `call`-object
worden niet verzonden.

Retourneer de response-envelop van Vapi met de overeenkomende tool-call-ID:

```json
{
  "results": [
    {
      "toolCallId": "call_00112233445566778899aabbccddeeff",
      "result": "{\"available_slots\":[\"9:00 AM\",\"2:00 PM\"]}"
    }
  ]
}
```

ThunderPhone geeft de AI de onverpakte `result`-tekenreeks. Het accepteert ook een
JSON-waarde voor compatibiliteit met endpoints die er al een retourneren. Een
`error` in plaats van `result` wordt als toolfout aan de AI teruggegeven. Als het
endpoint een onjuist gevormde of niet-overeenkomende Vapi-envelop retourneert, registreert ThunderPhone
een waarschuwing en valt het terug op de standaardverwerking van responses in plaats van
de oproep te laten mislukken.

#### Retell

`retell` verstuurt de aanvraag die is gedocumenteerd voor een [aangepaste functie van
Retell](https://docs.retellai.com/build/single-multi-prompt/custom-function):

```json
{
  "name": "search_appointments",
  "call": { "call_id": "987654321" },
  "args": { "date": "2025-01-02", "service": "consultation" }
}
```

Het `call`-object bevat alleen `call_id`. ThunderPhone voegt geen
Retell-specifieke velden toe die het bij verzending niet eerlijk kan leveren, zoals
`agent_id`, `transcript`, `transcript_object`, `metadata` of dynamische
variabelen. Retell accepteert elke responsebody; ThunderPhone geeft deze
door met de standaard responseverwerking.

Voor `GET` / `DELETE` worden de argumenten verzonden als **queryparameters**
en is de body leeg, ongeacht `request_format`. De V2-handtekening
dekt de querytekenreeks zoals verzonden; de verouderde handtekening wordt berekend over
de lege bytestring. Zie
[Webhookhandtekeningen verifiëren](/nl/guides/verify-webhook-signatures).

### Antwoord

Retourneer een JSON-antwoord met het toolresultaat:

```json
{
  "available_slots": ["9:00 AM", "2:00 PM", "4:30 PM"],
  "timezone": "America/Los_Angeles"
}
```

Het antwoord wordt opgemaakt en aan de AI verstrekt om het gesprek
voort te zetten. Niet-JSON-antwoorden worden verpakt als `{"data": "<text>"}`;
time-outs en verbindingsfouten worden als fouten aan de AI gemeld, zodat
de agent zich kan verontschuldigen en verder kan gaan in plaats van vast te lopen.

## Dispatch in webhookmodus

Tools **zonder** een `endpoint` worden naar de verouderde webhook-URL
van je organisatie gestuurd als een ondertekend `telephony.tool`-verzoek
(telefoongesprekken) of `web.tool`-verzoek (webgesprekken). Anders dan de
[auditmeldingen](/nl/webhooks/events) die na uitvoering bij webhookendpoints
worden afgeleverd, **is** dit verzoek de uitvoering — je HTTP-antwoord
is het toolresultaat.

```json
{
  "type": "telephony.tool",
  "data": {
    "call_id": 987654321,
    "tool_name": "search_appointments",
    "arguments": { "date": "2026-04-21" },
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  }
}
```

`web.tool` bevat `origin_domain` in plaats van `from_number` /
`to_number`. Reageer met het toolresultaat als JSON — hetzelfde responscontract
als bij rechtstreekse endpointaanroepen. Het verzoek is ondertekend met het webhookgeheim van de organisatie, met beide handtekeningheaders, net als elke andere webhook.

<Note>
  Geabonneerde [webhookendpoints](/nl/webhooks/endpoints) ontvangen daarnaast
  na elke uitvoering van een tool (ongeacht welk pad deze uitvoerde) een
  niet-blokkerende `telephony.tool` / `web.tool`-**melding**, inclusief
  het antwoord van de tool — nuttig voor audittrails. Zie de
  [gebeurteniscatalogus](/nl/webhooks/events).
</Note>

---

## Handtekeningverificatie

Directe toolaanroepen worden op dezelfde manier ondertekend als webhooks, met het
webhookgeheim van je organisatie als sleutel:

- `X-ThunderPhone-Signature-V2` ondertekent de tijdstempel, methode, het pad en
  de query zoals verzonden, plus de exacte bytes van de body. Weiger aanvragen waarvan de
  tijdstempel meer dan 5 minuten afwijkt van je klok.
- `X-ThunderPhone-Signature` (verouderd) ondertekent alleen de bytes van de body,
  of de lege bytereeks voor `GET` / `DELETE`. Deze wordt nog steeds ongewijzigd verzonden.

De SDK-helpers controleren de V2-header, inclusief het venster van 5 minuten:

<CodeGroup>
```python Python
import json

from fastapi import FastAPI, HTTPException, Request
from thunderphone import verify_signature

app = FastAPI()

@app.post("/appointments/search")
async def search_appointments(request: Request):
    body = await request.body()
    query = request.scope.get("query_string", b"").decode("latin-1")
    path = request.url.path + (f"?{query}" if query else "")

    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=WEBHOOK_SECRET,
        method=request.method,
        path=path,
        body=body,
    ):
        raise HTTPException(status_code=401)

    data = json.loads(body)
    date = data["date"]

    # Look up availability
    slots = await get_available_slots(date)

    return {"available_slots": slots}
```

```javascript Node.js
import express from "express";
import { verifySignature } from "@thunderphone/sdk";

app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
  const ok = verifySignature({
    header: req.header('X-ThunderPhone-Signature-V2'),
    secret: WEBHOOK_SECRET,
    method: req.method,
    path: req.originalUrl,
    body: req.body,
  });
  if (!ok) return res.status(401).send('Invalid signature');

  const { date, service } = JSON.parse(req.body);

  // Look up availability
  const slots = getAvailableSlots(date, service);

  res.json({ available_slots: slots });
});
```
</CodeGroup>

Volledige recepten — de ondertekende tekenreeks, een uitgewerkt voorbeeld, code zonder de
SDK, het geval `GET` / `DELETE` en de kanttekening over geen geheim — vind je in [Webhookhandtekeningen verifiëren](/nl/guides/verify-webhook-signatures).

---

## Voorbeeld: volledige boekingsflow

Hier is een set tools voor een volledig systeem voor afspraakboekingen:

```json
{
  "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": "key" }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "book_appointment",
        "description": "Book an appointment at a specific time",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "time": { "type": "string", "description": "HH:MM format" },
            "customer_name": { "type": "string" },
            "customer_phone": { "type": "string" }
          },
          "required": ["date", "time", "customer_name"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/book",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "cancel_appointment",
        "description": "Cancel an existing appointment",
        "parameters": {
          "type": "object",
          "properties": {
            "confirmation_number": { "type": "string" }
          },
          "required": ["confirmation_number"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/cancel",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    }
  ]
}
```

---

## Best practices

<AccordionGroup>
  <Accordion title="Schrijf duidelijke beschrijvingen">
    Het veld `description` helpt de AI te begrijpen **wanneer** de tool moet worden gebruikt. Wees specifiek over wat de tool doet en wanneer deze geschikt is.
  </Accordion>

  <Accordion title="Ga zorgvuldig om met fouten">
    Geef foutmeldingen terug die de AI kan begrijpen: `{"error": "No slots available for that date"}` in plaats van algemene 500-fouten.
  </Accordion>

  <Accordion title="Houd reacties beknopt">
    Geef alleen terug wat de AI nodig heeft om het gesprek voort te zetten. Grote payloads vertragen de responstijden.
  </Accordion>

  <Accordion title="Gebruik verplichte velden verstandig">
    Markeer velden alleen als `required` wanneer dat echt nodig is. De AI vraagt de gebruiker om verplichte informatie voordat de tool wordt aangeroepen.
  </Accordion>
</AccordionGroup>

---

## Gerelateerd

<CardGroup cols={2}>
  <Card title="Ingebouwde tools" icon="wrench" href="/nl/guides/built-in-tools">
    Start door het platform beheerde oproepacties zonder een endpoint te definiëren.
  </Card>
  <Card title="App-koppelingen" icon="plug" href="/nl/guides/connect-apps">
    Door het platform beheerde tools voor HubSpot, Salesforce, Slack, Google
    Calendar, Google Sheets en Cal.com — geen endpoint vereist.
  </Card>
  <Card title="MCP-servers" icon="server" href="/nl/guides/mcp-servers">
    Koppel een MCP-server en laat de agent de tools ervan aanroepen.
  </Card>
  <Card title="API-koppelingen" icon="code" href="/nl/guides/api-connections">
    Herbruikbare REST-integraties die je aan agents kunt koppelen.
  </Card>
  <Card title="Webhookhandtekeningen verifiëren" icon="shield-check" href="/nl/guides/verify-webhook-signatures">
    Eén verificatiehulp voor webhooks en toolaanroepen.
  </Card>
</CardGroup>
