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

# Function calling

> Laat je chatbot tijdens een gesprek je eigen API aanroepen.

Je kennisbank bevat de regels. Function calling haalt de actuele stand op: een
orderstatus, een voorraad, een vrij tijdslot. Jij geeft een adres en zegt
wanneer de bot het moet gebruiken; Chatwize roept het aan tijdens het gesprek.

## Waar vind je het

<Steps>
  <Step title="Open je chatbot">
    Klik vanuit **Chatbots** op je chatbot.
  </Step>

  <Step title="Open AI-agent">
    In de linker zijbalk, onder **Hoofdmenu**. Open de agent die dit moet kunnen.
  </Step>

  <Step title="Voeg een endpoint toe">
    Onder **Outbound API-endpoints**, klik **Voeg endpoint toe**.
  </Step>
</Steps>

## Wat je invult

| Veld                                   | Wat je invult                                                          |
| -------------------------------------- | ---------------------------------------------------------------------- |
| **Slug**                               | Naam voor de AI, in `snake_case`: `zoek_bestelling`. Ligt daarna vast. |
| **Naam**                               | Naam voor jezelf.                                                      |
| **Wanneer moet de bot dit aanroepen?** | Het belangrijkste veld. Wat de API doet én in welke situatie.          |
| **Method**                             | `GET` om op te zoeken. `POST`, `PUT` of `PATCH` als er iets wijzigt.   |
| **URL**                                | Het volledige adres, met `https://`. Publiek bereikbaar.               |
| **Allowlist host**                     | De hostnaam uit de URL, letterlijk gelijk.                             |
| **Parameters**                         | JSON-Schema met wat de AI mag invullen. Leeg = geen parameters.        |
| **Type actie**                         | **Alleen lezen** of **Voert een actie uit (write)**.                   |
| **Automatisch uitvoeren**              | Alleen bij write. Staat **standaard aan** — zet uit zolang je test.    |
| **Max. aanroepen per gesprek**         | Rem tegen een bot die blijft aanroepen.                                |
| **Headers**                            | Je eigen headers, zoals `Authorization`.                               |

<Tip>
  Schrijf de beschrijving als opdracht: "Zoekt een bestelling op via het
  ordernummer. Roep dit aan als de bezoeker vraagt waar zijn bestelling blijft."
  Niet: "Order-API".
</Tip>

## Voorbeeld

Orderopzoeking op `https://api.voorbeeld.nl/orders`.

```json Parameters theme={null}
{
  "type": "object",
  "properties": {
    "order_number": {
      "type": "string",
      "description": "Het ordernummer zoals de bezoeker het noemt, bijvoorbeeld 10423"
    }
  },
  "required": ["order_number"]
}
```

Chatwize roept `…/orders?order_number=10423` aan. Jij antwoordt:

```json theme={null}
{ "status": "verzonden", "vervoerder": "PostNL", "verwacht": "2026-09-25" }
```

De bezoeker leest: *"Je bestelling is met PostNL onderweg en wordt 25 september
verwacht."*

<AccordionGroup>
  <Accordion title="Meer voorbeelden">
    **Voorraad** — "Geeft de voorraad van één artikel. Roep dit aan als de
    bezoeker vraagt of iets op voorraad is."

    ```json theme={null}
    {
      "type": "object",
      "properties": {
        "artikelcode": { "type": "string", "description": "De artikelcode zoals op de site, bijvoorbeeld AB-1234" }
      },
      "required": ["artikelcode"]
    }
    ```

    **Beschikbaarheid** — een `enum` houdt de bot binnen jouw categorieën.

    ```json theme={null}
    {
      "type": "object",
      "properties": {
        "categorie": { "type": "string", "enum": ["compact", "gezin", "luxe"], "description": "Het type waar de bezoeker naar vraagt" },
        "van": { "type": "string", "description": "Startdatum als JJJJ-MM-DD" },
        "tot": { "type": "string", "description": "Einddatum als JJJJ-MM-DD" }
      },
      "required": ["categorie", "van", "tot"]
    }
    ```

    Een datum komt binnen als tekst. Controleer zelf of hij klopt.

    **Terugbelverzoek** — een write-actie via `POST`. Voorkom dubbele verzoeken
    aan jouw kant.

    ```json theme={null}
    {
      "type": "object",
      "properties": {
        "naam": { "type": "string", "description": "De naam die de bezoeker opgeeft" },
        "telefoon": { "type": "string", "description": "Het telefoonnummer dat de bezoeker opgeeft" }
      },
      "required": ["naam", "telefoon"]
    }
    ```

    **Geen parameters** — `{}`. Bijvoorbeeld openingstijden. Het beste endpoint
    om mee te beginnen.
  </Accordion>

  <Accordion title="Een webshop koppelen">
    WooCommerce: `https://jouwwinkel.nl/wp-json/wc/v3/orders` met parameter
    `search`, en je consumer key en secret in een `Authorization`-header.
    Shopify en Magento werken hetzelfde: `GET` met query-parameters, token in een
    header.

    Vaste waarden zet je zelf in de URL (`?per_page=1`); die kan de AI niet
    overschrijven.

    <Warning>
      Een leessleutel ziet **alle** bestellingen — wie een ordernummer raadt,
      krijgt de status. Zet er je eigen endpoint voor dat ordernummer én
      e-mailadres eist en alleen een paar velden teruggeeft.
    </Warning>
  </Accordion>
</AccordionGroup>

## Het schema

Eén plat object. Properties van het type `string`, `number`, `integer` of
`boolean`, eventueel met `enum`. Geen geneste objecten of arrays. De
`description` is wat de AI leest.

Alles in het schema kan de bezoeker beïnvloeden met wat hij typt. Dit hoort er
dus nooit in:

| Nooit een parameter           | Waar het wel hoort                                |
| ----------------------------- | ------------------------------------------------- |
| Sleutel, token, wachtwoord    | Header                                            |
| Klant-, account- of tenant-ID | Vast in de URL, of bepaald door je eigen endpoint |
| `admin`, `debug`              | Nergens                                           |
| `per_page` en andere limieten | Vast in de URL                                    |

<Warning>
  Een parameter `klant_id` wordt gevuld met wat de bezoeker zegt. Wíé de gegevens
  krijgt, bepaalt je eigen systeem — nooit het gesprek.
</Warning>

## Wat je binnenkrijgt

JSON over HTTPS, met jouw headers en een ondertekening. Controleer die: dan weet
je dat het verzoek van Chatwize komt en niet is gewijzigd. De sleutel zie je
één keer, bij het aanmaken.

<AccordionGroup>
  <Accordion title="Ondertekening controleren">
    Twee headers: `X-Chatwize-Outbound-Timestamp` (moment van versturen) en
    `X-Chatwize-Outbound-Signature` (`sha256=` plus een HMAC-SHA256 over
    `<tijdstempel>.<inhoud>`). De inhoud is de rauwe body bij `POST`, `PUT` en
    `PATCH`, en de query-string bij `GET`.

    <CodeGroup>
      ```js Node.js theme={null}
      import { createHmac, timingSafeEqual } from "node:crypto";

      function isVanChatwize(secret, req, rawBody) {
        const ts = req.headers["x-chatwize-outbound-timestamp"];
        const ontvangen = req.headers["x-chatwize-outbound-signature"] ?? "";
        if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

        const inhoud = req.method === "GET" ? new URL(req.url, "https://x").search : rawBody;
        const verwacht = "sha256=" + createHmac("sha256", secret)
          .update(`${ts}.${inhoud}`).digest("hex");

        if (verwacht.length !== ontvangen.length) return false;
        return timingSafeEqual(Buffer.from(verwacht), Buffer.from(ontvangen));
      }
      ```

      ```python Python theme={null}
      import hmac, hashlib, time

      def is_van_chatwize(secret: str, headers, inhoud: str) -> bool:
          ts = headers.get("X-Chatwize-Outbound-Timestamp", "")
          ontvangen = headers.get("X-Chatwize-Outbound-Signature", "")
          if not ts or abs(time.time() - int(ts)) > 300:
              return False
          digest = hmac.new(secret.encode(), f"{ts}.{inhoud}".encode(), hashlib.sha256).hexdigest()
          return hmac.compare_digest(f"sha256={digest}", ontvangen)
      ```

      ```php PHP theme={null}
      function isVanChatwize(string $secret, array $headers, string $inhoud): bool {
          $ts = $headers['X-Chatwize-Outbound-Timestamp'] ?? '';
          $ontvangen = $headers['X-Chatwize-Outbound-Signature'] ?? '';
          if ($ts === '' || abs(time() - (int) $ts) > 300) return false;
          $verwacht = 'sha256=' . hash_hmac('sha256', "$ts.$inhoud", $secret);
          return hash_equals($verwacht, $ontvangen);
      }
      ```
    </CodeGroup>

    Onderteken over de rauwe body, vergelijk met `timingSafeEqual` /
    `compare_digest` / `hash_equals` (nooit `==`), en neem bij `GET` het
    vraagteken mee.
  </Accordion>
</AccordionGroup>

## Wat je terugstuurt

JSON, binnen vijf seconden, klein. Alleen het begin van een lang antwoord
bereikt de AI. Alles wat je stuurt kan in de chat belanden.

## Veilig koppelen

<Warning>
  Wat je bot mag aanroepen, kan elke bezoeker indirect laten aanroepen.
</Warning>

* **Controleer zelf of het mag.** De ondertekening bewijst dat het van Chatwize
  komt, niet dat deze bezoeker recht heeft op deze gegevens.
* **Liever lezen dan schrijven.** Automatisch uitvoeren uit zolang je test.
* **Stuur zo weinig mogelijk terug.**
* **Aparte sleutel in een header**, met zo min mogelijk rechten.
* **Publiek adres dat direct antwoordt.** Interne adressen en doorverwijzingen
  werken niet.

## Als het niet werkt

| Wat je ziet              | Meestal                                                                                                              |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| Bot roept het nooit aan  | De beschrijving. Schrijf de situatie uit in de woorden van de bezoeker. Staat het endpoint aan, bij de juiste agent? |
| Aanroep vertrekt niet    | URL-host en allowlist host zijn niet letterlijk gelijk.                                                              |
| Controle mislukt steeds  | Verkeerde bytes: geparste body, of query-string zonder vraagteken.                                                   |
| Time-out                 | Je API is te traag. Bewaar het antwoord aan jouw kant.                                                               |
| Write-actie gebeurt niet | Automatisch uitvoeren staat uit.                                                                                     |
| Parameters geweigerd     | Past niet bij je schema. Scherp de beschrijvingen aan.                                                               |

Elke aanroep staat in de inbox: welk endpoint, welke parameters, wat er
terugkwam.
