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.
Open your Finn, click Edit Workflow, and drag an Action node onto the canvas. Set its type to Webhook. Configure:
| Field | What it does |
|---|---|
| URL | The endpoint the Finn calls |
| Method | HTTP method for the request |
| Body | What to send. Supports {{contact.*}} and {{state.*}} variables |
| Headers | Static headers, including your own auth header |
The response becomes available as workflow variables downstream. See workflow variables for how {{state.*}} naming works.
Request and response
The body you configure is sent as JSON. Variables are substituted before the request goes out.
{
"phone_number": "{{contact.phone_number}}",
"order_reference": "{{state.order_ref}}"
}
Your endpoint returns JSON. Top-level keys become workflow variables.
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);
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 a flag the workflow can branch on (found: false above) rather than a 404 or an empty body. Then add a Decision node on that flag with a path for the miss.
The variable name does not match. A Say node referencing {{state.order_status}} when your endpoint returned orderStatus prints the literal {{state.order_status}} into the call. Names must match exactly, including case.
You send something you should not. The request body is built 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.
The exact timeout and retry behavior for in-call tool requests is not settled in the platform docs. Treat your endpoint as if it gets one attempt with a short budget, and design for the failure path.
Testing
Use Test on Chat in the workflow editor first. Each node shows its inputs and outputs, so you can see the request body your Finn actually sent and the object it got back. That catches variable typos and shape mismatches without burning a phone call.
Then use Test on Call. In-call latency only shows up on a real call. A lookup that reads fine in chat can 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, SMS, CRM writes
- Transfer — handing the call to a person
- Webhooks — post-call events, not in-call lookups