What a custom tool is
A custom tool lets your Finn call an HTTP endpoint you control while a conversation is happening. The Finn sends a request, waits for your response, and uses the returned data in what it says next.
The common case is live data the platform does not hold. A caller asks about their order. Order status lives in your system. The Finn calls your endpoint with the caller's phone number, your endpoint returns {"order_status": "shipped", "tracking": "1Z999..."}, and the Finn says the order shipped yesterday.
Custom tools are different from webhooks. Webhooks fire after something happens and your endpoint's response is ignored. A custom tool blocks the conversation until your endpoint answers, and the answer changes what the Finn says.
Adding one
Custom tools are configured in the dashboard, not through the API. There is no endpoint for creating tools.
Tools only run on a Finn with a call flow. Open your Finn, click Edit, and on the Call flow tab open the canvas (Convert to conversational flow, or Callflow canvas if it has one). Select the Conversation step where the lookup should happen, and attach a tool to it. A tool is not a separate step on the canvas. The Finn calls it inline during that Conversation step, then keeps talking.
There are two custom tool kinds:
| Kind | Behavior |
|---|---|
API request (api_request) | The Finn waits for your response and uses it in what it says next. |
Webhook (webhook) | Fire-and-forget. The Finn does not wait for or read the response. |
Configure:
| Field | What it does |
|---|---|
| What does this do? | A description of the tool. The AI uses it to decide when to call the tool. |
| URL | The endpoint the Finn calls |
| Method | GET, POST, PUT, PATCH, or DELETE |
| Headers | Static headers, including your own auth header. Values are stored in the call flow as typed and appear in Export JSON, so treat an export as a secret. |
| Parameters | Values the AI extracts from the call and sends to your API. Each has a name, type (string, number, boolean), description, and a required flag. |
| Filler | What the Finn says while the request is in flight, for example "One moment…" |
| Timeout (ms) | How long the Finn waits for your endpoint |
Request and response
You do not write a request body template. You declare parameters, and the AI fills them from the conversation. Write each parameter's description so the AI knows what to put there, for example phone_number: "The caller's phone number in E.164 format."
How the parameters reach your endpoint depends on the method. A GET tool sends them as query-string parameters. POST, PUT, PATCH, and DELETE send them as a JSON body with each parameter as a top-level key.
Your endpoint must be publicly reachable over HTTPS (no VPN or IP allowlist), return JSON, and respond within the timeout.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.post("/lookup")
def lookup():
payload = request.get_json()
phone = payload["phone_number"]
order = db.find_latest_order(phone)
if order is None:
return jsonify({"found": False, "order_status": "unknown"})
return jsonify({
"found": True,
"order_status": order.status,
"tracking": order.tracking_number,
})
if __name__ == "__main__":
app.run(port=8080)
A TypeScript equivalent:
import express from "express";
const app = express();
app.use(express.json());
app.post("/lookup", async (req, res) => {
const { phone_number: phone } = req.body;
const order = await db.findLatestOrder(phone);
if (!order) {
res.json({ found: false, order_status: "unknown" });
return;
}
res.json({
found: true,
order_status: order.status,
tracking: order.trackingNumber,
});
});
app.listen(8080);
Both examples handle a POST tool. For a GET tool, read the same keys from the query string instead: request.args["phone_number"] in Flask, req.query.phone_number in Express.
For an API request tool, the Finn reads the JSON you return and uses it in its next reply. Keep it small and use clear key names.
Test the shape from your terminal before wiring it into a workflow:
curl -X POST https://api.example.com/lookup \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your_token" \
-d '{"phone_number": "+15551234567", "order_reference": "ORD-4471"}'
What will go wrong
Your endpoint is slow. The caller is on the line. Every second your endpoint takes is a second of silence. Anything that queries a slow upstream system will be heard as the Finn hanging. Cache what you can and return partial data rather than waiting for a complete answer.
Your endpoint returns nothing useful. A record that does not exist is a normal outcome, not an error. Return an explicit flag (found: false above) rather than a 404 or an empty body, and tell the Finn in the Conversation step's instructions what to say when nothing is found.
The parameter description is vague. The AI fills parameters from what it heard. A parameter named ref with no description gets guessed at. Describe the expected value and format.
You send something you should not. Parameters are filled from live call data. Do not put full card numbers, national ID numbers, or health details in it unless your endpoint is cleared to receive them. See compliance.
Retry behavior for in-call tool requests is not documented. Treat your endpoint as if it gets one attempt within the timeout you set, and design for the failure path.
Testing
Check your endpoint's response shape with curl first, as above. Simulation chat doesn't run tools, so it can't test the request.
Then use Test → Phone call on the Finn's page. It runs the saved canvas with its tools, and in-call latency only shows up on a real call. A lookup that returns quickly from your terminal can still leave a three-second gap on the phone.
For pointing a Finn at an endpoint running on your machine, see local development.
Related
- Tools overview — how tools fit into a conversation
- Built-in tools — calendar booking and call transfer
- Transfer — handing the call to a person
- Webhooks — post-call events, not in-call lookups