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

> Let your chatbot call your own API during a conversation.

Your knowledge base holds the rules. Function calling fetches the current state:
an order status, a stock level, a free slot. You give an address and say when
the bot should use it; Chatwize calls it during the conversation.

## Where to find it

<Steps>
  <Step title="Open your chatbot">
    From **Chatbots**, click your chatbot.
  </Step>

  <Step title="Open AI agent">
    In the left sidebar, under **Main menu**. Open the agent that should get
    this ability.
  </Step>

  <Step title="Add an endpoint">
    Under **Outbound API endpoints**, click **Add endpoint**.
  </Step>
</Steps>

## What you fill in

| Field                              | What to enter                                                              |
| ---------------------------------- | -------------------------------------------------------------------------- |
| **Slug**                           | Name for the AI, in `snake_case`: `lookup_order`. Fixed once created.      |
| **Name**                           | Name for yourself.                                                         |
| **When should the bot call this?** | The most important field. What the API does and in which situation.        |
| **Method**                         | `GET` to look something up. `POST`, `PUT` or `PATCH` if something changes. |
| **URL**                            | The full address, with `https://`. Publicly reachable.                     |
| **Allowlist host**                 | The hostname from the URL, exactly the same.                               |
| **Parameters**                     | JSON Schema of what the AI may fill in. Empty = no parameters.             |
| **Action type**                    | **Read only** or **Performs an action (write)**.                           |
| **Automatic calling**              | Write only. **On by default** — switch off while testing.                  |
| **Max. calls per conversation**    | Brake against a bot that keeps calling.                                    |
| **Headers**                        | Your own headers, such as `Authorization`.                                 |

<Tip>
  Write the description as an instruction: "Looks up an order by order number.
  Call this when the visitor asks where their order is." Not: "Order API".
</Tip>

## Example

Order lookup on `https://api.example.com/orders`.

```json Parameters theme={null}
{
  "type": "object",
  "properties": {
    "order_number": {
      "type": "string",
      "description": "The order number as the visitor states it, for example 10423"
    }
  },
  "required": ["order_number"]
}
```

Chatwize calls `…/orders?order_number=10423`. You reply:

```json theme={null}
{ "status": "shipped", "carrier": "PostNL", "expected": "2026-09-25" }
```

The visitor reads: *"Your order is on its way with PostNL and expected on 25
September."*

<AccordionGroup>
  <Accordion title="More examples">
    **Stock** — "Returns the stock of one product. Call this when the visitor
    asks whether something is in stock."

    ```json theme={null}
    {
      "type": "object",
      "properties": {
        "product_code": { "type": "string", "description": "The product code as shown on the site, for example AB-1234" }
      },
      "required": ["product_code"]
    }
    ```

    **Availability** — an `enum` keeps the bot within your categories.

    ```json theme={null}
    {
      "type": "object",
      "properties": {
        "category": { "type": "string", "enum": ["compact", "family", "luxury"], "description": "The type the visitor asks about" },
        "from": { "type": "string", "description": "Start date as YYYY-MM-DD" },
        "until": { "type": "string", "description": "End date as YYYY-MM-DD" }
      },
      "required": ["category", "from", "until"]
    }
    ```

    A date arrives as text. Check it yourself.

    **Callback request** — a write action via `POST`. Prevent duplicate
    requests on your side.

    ```json theme={null}
    {
      "type": "object",
      "properties": {
        "name": { "type": "string", "description": "The name the visitor gives" },
        "phone": { "type": "string", "description": "The phone number the visitor gives" }
      },
      "required": ["name", "phone"]
    }
    ```

    **No parameters** — `{}`. Opening hours, for example. The best endpoint to
    start with.
  </Accordion>

  <Accordion title="Connecting a webshop">
    WooCommerce: `https://yourstore.com/wp-json/wc/v3/orders` with parameter
    `search`, and your consumer key and secret in an `Authorization` header.
    Shopify and Magento work the same way: `GET` with query parameters, token in
    a header.

    Fixed values go in the URL yourself (`?per_page=1`); the AI cannot overwrite
    those.

    <Warning>
      A read key sees **every** order — anyone who guesses an order number gets
      its status. Put your own endpoint in front that requires order number and
      email address and returns only a few fields.
    </Warning>
  </Accordion>
</AccordionGroup>

## The schema

One flat object. Properties of type `string`, `number`, `integer` or `boolean`,
optionally with an `enum`. No nested objects or arrays. The `description` is
what the AI reads.

Everything in the schema can be influenced by what the visitor types. So this
never belongs in it:

| Never a parameter              | Where it belongs                                   |
| ------------------------------ | -------------------------------------------------- |
| Key, token, password           | Header                                             |
| Customer, account or tenant ID | Fixed in the URL, or resolved by your own endpoint |
| `admin`, `debug`               | Nowhere                                            |
| `per_page` and other limits    | Fixed in the URL                                   |

<Warning>
  A parameter `customer_id` gets filled with whatever the visitor says. Who gets
  the data is decided by your own system — never by the conversation.
</Warning>

## What you receive

JSON over HTTPS, with your headers and a signature. Verify the signature: then
you know the request came from Chatwize and was not altered. You see the secret
once, when you create the endpoint.

<AccordionGroup>
  <Accordion title="Verifying the signature">
    Two headers: `X-Chatwize-Outbound-Timestamp` (moment of sending) and
    `X-Chatwize-Outbound-Signature` (`sha256=` plus an HMAC-SHA256 over
    `<timestamp>.<content>`). The content is the raw body for `POST`, `PUT` and
    `PATCH`, and the query string for `GET`.

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

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

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

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

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

      def is_from_chatwize(secret: str, headers, content: str) -> bool:
          ts = headers.get("X-Chatwize-Outbound-Timestamp", "")
          received = 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}.{content}".encode(), hashlib.sha256).hexdigest()
          return hmac.compare_digest(f"sha256={digest}", received)
      ```

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

    Sign over the raw body, compare with `timingSafeEqual` / `compare_digest` /
    `hash_equals` (never `==`), and include the `?` for `GET`.
  </Accordion>
</AccordionGroup>

## What you send back

JSON, within five seconds, small. Only the start of a long response reaches
the AI. Anything you send can end up in the chat.

## Keep it safe

<Warning>
  Anything your bot may call, any visitor can indirectly cause it to call.
</Warning>

* **Authorise on your side.** The signature proves it came from Chatwize, not
  that this visitor is entitled to this data.
* **Prefer read over write.** Automatic calling off while testing.
* **Return as little as possible.**
* **Separate key in a header**, with as few rights as possible.
* **Public address that answers directly.** Internal addresses and redirects do
  not work.

## When it does not work

| What you see                 | Usually                                                                                                      |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Bot never calls it           | The description. Write out the situation in the visitor's words. Is the endpoint on, and on the right agent? |
| Call never leaves            | URL host and allowlist host are not exactly the same.                                                        |
| Verification keeps failing   | Wrong bytes: parsed body, or query string without the `?`.                                                   |
| Timeout                      | Your API is too slow. Cache the answer on your side.                                                         |
| Write action does not happen | Automatic calling is off.                                                                                    |
| Parameters refused           | Does not match your schema. Sharpen the descriptions.                                                        |

Every call is in the inbox: which endpoint, which parameters, what came back.
