> ## Documentation Index
> Fetch the complete documentation index at: https://docs.voice.wixzel.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Give your AI phone agent tools: webhooks and app actions

> Let a voice agent take actions during a call: a webhook tool calls your server (on phone and web calls), a client tool runs in the caller's app or on your website. Create them with /v1/tools and attach them with tool_ids.

A tool is something your agent can do during a call besides talking: look up
an order, book in your own system, open a page in your app. The agent decides
when to use one from its description, fills in the parameters from the
conversation, and tells the caller what came back.

| Kind | Runs | Works on | Good for |
| - | - | - | - |
| **Webhook** | On your server: we send the arguments, you answer | Phone and web calls | Looking things up and making changes in your own systems |
| **Client** | In the caller's app, or on your website with your agent's [mascot](/mascots.md) | Web and app calls only | Opening a page, showing a product, filling a form on screen |

Connected apps (HubSpot, Stripe and the rest) are tools too: see
[MCP integrations](/mcp-integrations.md). The built-ins (ending the call,
transfers, booking appointments) are always there when switched on, and an
agent whose knowledge base has [files](/knowledge-bases.md#files) gets
`search_knowledge` to look things up in them.

The console does all of this under **Tools**, and on each agent's page under
**Tools**.

## Create a webhook tool

```bash theme={null}
curl https://api.voice.wixzel.com/v1/tools \
  -H "Authorization: Bearer $WIXZEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "check_order_status",
    "description": "Look up where an order is. Use it when the caller asks about a delivery.",
    "type": "webhook",
    "parameters": {
      "type": "object",
      "properties": { "order_id": { "type": "string", "description": "The order number the caller reads out" } },
      "required": ["order_id"]
    },
    "webhook": {
      "url": "https://api.example.com/orders/status",
      "method": "POST",
      "headers": [{ "name": "Authorization", "value": "Bearer sk_live_..." }]
    }
  }'
```

Write the description for the agent: what the tool does and when to use it.
Describe every parameter too; the agent reads those to know what to fill in.

Then give it to an agent:

```bash theme={null}
curl -X PATCH https://api.voice.wixzel.com/v1/agents/AGENT_ID \
  -H "Authorization: Bearer $WIXZEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tool_ids": ["TOOL_ID"] }'
```

### What your endpoint receives

* **The arguments** as the JSON body, or as query parameters for `GET` and
  `DELETE`. Nothing else is wrapped around them, so an existing API or a
  Zapier, Make or n8n catch hook works as it is.
* **Headers** saying what it is about:

| Header | Value |
| - | - |
| `X-Wixzel-Tool` | The tool's name |
| `X-Wixzel-Call-Id` | The call it came from (`test` for a test run) |
| `X-Wixzel-Agent-Id` | The agent that used it |
| `X-Wixzel-Timestamp` | Unix seconds when it was sent |
| `X-Wixzel-Signature` | HMAC-SHA256 of `timestamp + "." + body`, hex, keyed with your [webhook signing secret](/webhooks.md) |

Answer with JSON or text, within the timeout (10 seconds by default, up to
30\). The caller hears silence while the agent waits, so keep it quick. The
first 4,000 characters go back to the agent. An error status or a timeout is
turned into a sentence the agent can say, such as "I could not check that
right now".

### Verify the signature

```js theme={null}
import crypto from 'node:crypto';

function fromWixzel(req, rawBody) {
  const ts = req.headers['x-wixzel-timestamp'];
  const expected = crypto.createHmac('sha256', process.env.WIXZEL_WEBHOOK_SECRET)
    .update(`${ts}.${rawBody}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-wixzel-signature'] ?? ''));
}
```

Requests are only signed once you have a signing secret: create one with
`POST /v1/webhook/rotate-secret` or on the console's webhook settings.

### Test it

```bash theme={null}
curl https://api.voice.wixzel.com/v1/tools/TOOL_ID/test \
  -H "Authorization: Bearer $WIXZEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "arguments": { "order_id": "A-1042" } }'
```

The request is exactly what a call sends, signature included. The answer
shows the status, the time it took, and what the agent would hear.

## Client tools

A client tool runs in the caller's app. During a [realtime session](/realtime.md)
the server sends `tool.call` with the arguments; your code does the thing and
answers with `tool.result`. Phone calls have no app, so client tools are never
offered on them.

```bash theme={null}
curl https://api.voice.wixzel.com/v1/tools \
  -H "Authorization: Bearer $WIXZEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "open_page",
    "description": "Take the visitor to a page of the website, such as pricing or contact.",
    "type": "client",
    "parameters": { "type": "object", "properties": { "path": { "type": "string", "description": "The page, like /pricing" } }, "required": ["path"] },
    "client": { "wait_for_result": false }
  }'
```

`wait_for_result: false` tells the agent "done" at once, which suits actions
like opening a page. Leave it `true` (the default) when the agent needs what
your app returns.

### In your app (browser SDK)

```ts theme={null}
import { RealtimeSession } from 'wixzel-voice/realtime';

const call = await RealtimeSession.connect(session, {
  tools: {
    open_page: ({ path }) => { router.push(String(path)); },
    read_cart: async () => ({ items: cart.items.length, total: cart.total }),
  },
});
```

Whatever a handler returns goes back to the agent (text, or anything
JSON-serialisable). Throw to tell it the action failed. A tool with no handler
is answered with an error, and the agent carries on without it.

### On your website (your agent's mascot)

The website widget is your agent's mascot. `data-mascot` is the mascot's
`public_id`, from the agent's `mascot` field once sharing is on (see
[Mascots](/mascots.md#share-it)).

```html theme={null}
<script>
  window.WixzelMascot = Object.assign(window.WixzelMascot || {}, {
    tools: {
      open_page: ({ path }) => { window.location.href = path; },
    },
  });
</script>
<script src="https://voice.wixzel.com/embed.js" data-mascot="m_..." async></script>
```

With `WixzelMascot.mount(element, { mascot, tools })` you can pass the handlers
to one agent's mascot instead.

## Limits

* Up to 20 of your own tools per agent, and 40 tools in all with connected apps.
* Up to 20 tool calls in one call.
* Tool names: letters, digits and underscores, starting with a letter, unique in the account.
* Up to 30 parameters, three levels deep.
* Header values are encrypted and never returned; only their names are.
* Webhook URLs must be public `https` or `http` on port 443 or 80.

## On the call record

Every tool call is on the call: `GET /v1/calls/{id}` lists `tool_calls` with
the tool, its kind (`webhook`, `client`, `integration` or `knowledge`), the arguments with
long values cut, whether it worked and how long it took. The console shows
them on the call's timeline.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.