Skip to main content
Search

Custom tools

Let the assistant look up and change data in your own systems through your API, tied to the customer's verified identity.

Custom tools let the assistant call your own API while it replies: look up an order, check a subscription, cancel a booking. You describe the endpoint once in Admin > AI > Tools, allow the tool for an assistant, and the panel adds the customer's identity to every request in headers that the model can neither see nor change.

For Shopify, WooCommerce and Cal.com you do not need your own endpoint: connect them in the cabinet, see Ready-made actions.

How a tool works

The model decides when to call a tool, based on the tool's name and description. It fills in the arguments, the panel sends the request to your endpoint, and the response goes back to the model as text, so the assistant can answer from it.

A tool is shared: you create it once under Tools and then tick it in the Tools list of every assistant that may use it. A successful response from your endpoint counts as a reliable source, just like a knowledge base article, so an answer built on your data is not stopped by the rule against unsupported answers (see How the assistant answers).

The Test tab of an assistant only searches the knowledge base and never calls tools. Try a new tool in a real conversation instead, for example in the chat on your website.

Creating a tool

Open Admin > AI > Tools and click New tool:

Field What to enter
Name The function name the model calls: letters, numbers, hyphen and underscore, up to 64 characters. Names of built-in tools, such as the knowledge base search or the handoff, are reserved.
Description What the tool does and when to use it. The model picks the tool by its name and this text, so be precise.
URL and Method Your endpoint and GET or POST (POST by default).
Headers Headers sent with every request, for example your API key. Values are encrypted at rest.
Parameters (JSON schema) The arguments the model may pass. Leave it blank for one free-text input, or click Insert example.
Enabled Only enabled tools are offered to assistants, Juno and Generate reply.
Require verified contact On by default, see below.

The Tools page lists every tool with its URL and status. The row menu has Edit, Disable or Enable, and Delete, so you can pause a tool without losing its settings.

A schema for an order lookup could look like this:

{
  "type": "object",
  "properties": {
    "order_number": {
      "type": "string",
      "description": "Order number from the confirmation email, digits only"
    }
  },
  "required": ["order_number"]
}

How arguments are sent

With GET, the arguments are added to the query string of the URL; parameters already written in the URL are kept as they are. With POST, they are sent as a JSON body.

Your endpoint has 20 seconds to answer; a call that takes longer counts as failed. Redirects are not followed, so your headers and the customer's identity never travel on to another address; a 3xx answer counts as a failure.

Identity headers

The panel adds these headers to tool requests on its own side. The model has no access to them and cannot alter them. A header the panel has no value for, such as the email of a customer who never gave one, is left out; X-Support-Contact-Verified is always sent:

Header Contains
X-Support-Contact-Id The contact's ID in your panel.
X-Support-Contact-Email The contact's current email.
X-Support-Contact-External-Id Your own user ID for this customer, when the panel knows it, for example after your website or app signed the customer in to the chat.
X-Support-Contact-Type contact or visitor.
X-Support-Contact-Verified true or false, always present. See below.
X-Support-Conversation-UUID The conversation the call belongs to. Use it to keep state between calls within one conversation.
X-Support-Inbox-Id The inbox the conversation came from.

Require verified contact

An email address typed into a chat proves nothing: anyone can claim someone else's address. With Require verified contact on, the assistant first checks that the customer owns the address. If the panel has no email for the customer, the assistant asks for it. Then it emails a 6-digit code and asks the customer to send it back in the conversation, and only after a correct code does the tool run.

  • A code is valid for 10 minutes and accepts 3 attempts.
  • Within 30 minutes, one conversation gets at most 3 codes to the same address and at most 6 in total.
  • Verification lasts 30 minutes; after that, a new code is needed. A customer who changes the email in the chat must confirm the new address.
  • When the address the customer gives already belongs to another contact, the panel does not write it into the current contact: the code goes to that address, and nothing is linked until the customer enters it. With the right code, a Telegram or WhatsApp contact is merged into the contact that owns the address, so the history is shared and later messages from that chat land in the same contact; a private note in the conversation says so. A website visitor is not merged, so the open chat does not break: the conversation counts as verified with that address, and an agent can merge the two contacts by hand.

Only one kind of customer counts as verified without a code: a customer whom your website or mobile app signed in to the chat (see Live chat widget and Mobile app chat). Customers in Telegram, WhatsApp and email always confirm with a code, because a messenger account or a sender address does not prove they are the customer in your system.

In an email conversation the code is sent from your support mailbox, in the same thread. In the web chat, Telegram and WhatsApp it is sent from the email set up for notifications: your own mail in Notifications > Email, or the EPAV mailbox when it is switched on in the cabinet. The EPAV mailbox sends customers only these codes, as a short plain-text email (see Notifications and the EPAV mailbox). With neither set up, the code cannot be sent and the tool does not run.

Turning the switch off asks for confirmation (Turn off customer authentication?). Do it only if your endpoint authenticates the customer by itself: with the switch off, anyone who can write in the chat can make the tool run for that contact.

Always check the verified header

The switch works only inside the panel: it keeps the assistant from running the tool until the customer has confirmed the address. Your endpoint cannot rely on it: with the switch off, or for a tool where it was never on, requests for customers who confirmed nothing still reach you, with X-Support-Contact-Verified: false.

So check this header in your endpoint on every call. Return personal data, or change anything in the customer's account, only when it is true. Calls started by an agent from Juno or Generate reply arrive with true, because a person runs them (see the last section).

Writing a custom tool

Filling in the form is quick; the real work is your endpoint. Follow three rules:

  1. Verify your shared secret before anything else. Add a header such as X-Api-Key in Headers and reject every request without the right value. Your endpoint is reachable from the internet, and this header is what proves the call came from your panel.
  2. Identify the customer by the headers only, not by the arguments. The arguments are written by the model from what the customer typed. If the model sends an account or order number, check that it belongs to the contact in the headers instead of trusting it.
  3. Require X-Support-Contact-Verified: true before you return anything you would not show a stranger, and before any change: a cancellation, a refund, a new address.

A minimal endpoint in Python (Flask) that follows the three rules:

from flask import Flask, jsonify, request

app = Flask(__name__)
SECRET = "your-shared-secret"

@app.post("/orders/status")
def order_status():
    if request.headers.get("X-Api-Key") != SECRET:
        return jsonify(error="unauthorized"), 401
    if request.headers.get("X-Support-Contact-Verified") != "true":
        return jsonify(error="customer is not verified"), 403

    email = request.headers.get("X-Support-Contact-Email", "")
    number = (request.get_json(silent=True) or {}).get("order_number", "")
    order = find_order(number)  # your own lookup

    # The order must belong to the verified contact, whatever number was asked for.
    if order is None or order.email != email:
        return jsonify(result="No order with this number for this customer.")
    return jsonify(status=order.status, delivery_date=order.delivery_date)

What your endpoint should return

Return small, flat JSON with only the fields the assistant needs. The response goes into the model's context: up to 1 MB is read, and anything over 64 KB is cut off before it reaches the model.

The status code tells whether the request worked, not whether something was found. A 2xx response is treated as data: the assistant answers from it. Anything else is treated as a failure: the assistant tells the customer it could not complete the request and, where the assistant may hand over, offers a person, which usually ends in a handoff.

A customer without orders is therefore a normal 2xx answer that contains an empty list or one sentence saying so. Keep error codes for requests that really failed: a wrong API key, a missing identifier, your own system being down.

The response body reaches the model as text either way, so write error messages for it. "No order found with that number" gives the assistant something to ask the customer about; an empty 500 response mostly leads to a handoff.

Tools for agents

A tool can also serve your agents. The Agent access group of the tool form has three switches; the first two are off by default:

  • Available in Copilot: Juno, the AI helper in the conversation sidebar, may call the tool;
  • Available in Generate Reply: the drafted reply may use it;
  • Require agent approval (on by default, available when one of the two above is on): the tool pauses before it runs, and the agent sees Review tool request with the arguments and chooses Approve and run or Reject. Keep it on for tools that change data.

When an agent runs a tool, no code is asked from the customer: the agent is in charge of that request, and it reaches your endpoint with the identity headers of the conversation's customer and X-Support-Contact-Verified: true. More in AI helpers for agents.

Was this article helpful?