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

# Functietools

> Laat je AI-agenten tijdens gesprekken externe API's aanroepen

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 te boeken of elke actie uit te voeren die je backend ondersteunt.

## Hoe het werkt

1. Definieer tools met een schema (welke argumenten de tool accepteert)
2. Geef een `endpoint`-configuratie op (waar ThunderPhone je API aanroept) — of laat deze weg om toolaanroepen op je organisatie-webhook 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 aan de AI om het gesprek voort te zetten

<Note>
  Functietools zijn de route waarbij je je eigen API meebrengt. ThunderPhone
  levert ook platformbeheerde tools waarvoor geen endpoint nodig is:
  [app-koppelingen](/nl/guides/connect-apps) (HubSpot, Salesforce, Slack,
  Google Calendar, Google Sheets, Cal.com),
  [API-koppelingen](/nl/guides/api-connections) en
  [MCP-servers](/nl/guides/mcp-servers).
</Note>

***

## Toolschema

Elke tool volgt deze structuur:

```json theme={null}
{
  "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"
    }
  }
}
```

### Functiedefinitie

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

### Endpointconfiguratie

| Veld      | Type   | Verplicht | Beschrijving                      |
| --------- | ------ | --------- | --------------------------------- |
| `url`     | string | Ja        | De URL van je API-endpoint        |
| `method`  | string | 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 |
| Body                          | **Alleen toolargumenten**                                                     | `telephony.tool` / `web.tool`-envelop                                                                  |
| Headers                       | Je `endpoint.headers` + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature` | `Content-Type` + `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 — met een time-out van **20 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.

## Directe endpointaanroepen

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

### Verzoekheaders

```http theme={null}
POST /appointments/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key
```

Aangepaste headers uit je `endpoint.headers` worden altijd woordelijk
opgenomen, plus twee headers met de ThunderPhone-naamruimte:

* `X-ThunderPhone-Signature` — HMAC-SHA256 van de exacte bytes van de
  verzoekbody, met je **webhookgeheim van de organisatie** als sleutel
* `X-ThunderPhone-Call-ID` — De ID van het huidige gesprek

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

<Warning>
  De handtekening gebruikt het webhookgeheim op organisatieniveau van
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook) als sleutel.
  Als je organisatie de legacy 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 legacy webhook om een geheim te verkrijgen, of plaats je eigen
  gedeelde geheim in `endpoint.headers`.
</Warning>

### Verzoekbody

Voor `POST` / `PUT` / `PATCH` bevat de body **alleen** de argumenten van de
tool (zonder wrapper), canoniek geserialiseerd (gesorteerde sleutels, compacte
scheidingstekens):

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

Voor `GET` / `DELETE` worden de argumenten als **queryparameters** verstuurd
en is de body leeg — de handtekening wordt dan berekend over de lege
bytestring. Zie
[Webhookhandtekeningen verifiëren](/nl/guides/verify-webhook-signatures).

### Respons

Retourneer een JSON-respons met het resultaat van de tool:

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

De respons wordt geformatteerd en aan de AI verstrekt om het gesprek voort
te zetten. Niet-JSON-responsen 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 als ondertekend `telephony.tool`-
(verzoeken voor telefoongesprekken) of `web.tool`-verzoek (webverzoeken)
naar de legacy webhook-URL van je organisatie verstuurd. In tegenstelling tot
de [auditmeldingen](/nl/webhooks/events) die na uitvoering naar webhookendpoints
worden geleverd, **is** dit verzoek de uitvoering — je HTTP-respons is het
toolresultaat.

```json theme={null}
{
  "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 directe endpointaanroepen. Het verzoek wordt, net als
elke andere webhook, met het webhookgeheim van de organisatie over de onbewerkte
body ondertekend.

<Note>
  Geabonneerde [webhookendpoints](/nl/webhooks/endpoints) ontvangen daarnaast
  na elke uitvoering van een tool (ongeacht via welk pad deze werd uitgevoerd)
  een niet-blokkerende `telephony.tool` / `web.tool`-**melding**, inclusief de
  respons van de tool — handig voor audittrails. Zie de
  [eventcatalogus](/nl/webhooks/events).
</Note>

***

## Handtekeningverificatie

Directe toolaanroepen worden op dezelfde manier ondertekend als webhooks:

* HMAC-SHA256 over de exacte bytes van de requestbody (de canonieke JSON —
  gesorteerde sleutels, geen extra witruimte)
* Met je webhookgeheim voor de organisatie als sleutel
* `GET`- / `DELETE`-tools ondertekenen de lege bytestring

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_tool_call(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, signature)

  @app.post("/appointments/search")
  async def search_appointments(request: Request):
      body = await request.body()
      signature = request.headers.get("X-ThunderPhone-Signature", "")

      if not verify_tool_call(body, signature, WEBHOOK_SECRET):
          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 theme={null}
  app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
    const signature = req.headers['x-thunderphone-signature'] || '';
    const expected = crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (!signature ||
        signature.length !== expected.length ||
        !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
      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 — inclusief het geval met een lege body 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 compleet systeem voor het boeken van afspraken:

```json theme={null}
{
  "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">
    Retourneer foutmeldingen die de AI kan begrijpen: `{"error": "No slots available for that date"}` in plaats van algemene 500-fouten.
  </Accordion>

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

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

***

## Gerelateerd

<CardGroup cols={2}>
  <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 agenten 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>
