When to use a tool
- Look up a CRM record, calendar slot, or knowledge-base entry mid-call
- Place an order, book a meeting, or write back to your system
- Branch the dialogue on real-world state (account tier, inventory, eligibility)
- Surface UI in the participant’s browser (open a doc, prefill a form)
- Navigate the visitor around your own site mid-conversation (example)
The Tool object
Example tool object
Execution modes
Conversational modes
A tool call can take 1–8 seconds. Two extra fields control what the persona does during and after the call so the conversation doesn’t go dead.on_call — what the persona does while the tool runs
static_filler is required when on_call = "static_filler" and forbidden otherwise.
on_resolve — what happens with the result
How it works at runtime
1. Create a tool
Most people do this in Developer → Tools → New tool in Studio; the API is here for provisioning tools from your own code.201 Created:
webhook_secret is never returned — only has_webhook_secret: true. Rotate
it any time by PATCHing the tool with a new value; every attached scenario
picks up the change instantly.
2. Attach to a scenario
201 Created (or 200 OK if already attached):
Per-scenario fields
3. Handle the webhook
When the LLM calls the tool, CoreBackend POSTs this payload to yourwebhook_url:
Request headers
Respond with the result
Return any JSON withintimeout_ms. Your response body IS the tool result
the LLM sees on its next turn.
- Non-JSON body → wrapped as
{ "result": "<your-text>" } - Responses larger than 256 KB are truncated
- Non-2xx response → LLM sees
{ status: "error", error_code: "http_error", http_status: 500 }and recovers gracefully
Verify the signature
Client-mode tools
Setexecution_mode: "client" and omit webhook_url — in Studio, that is
Developer → Tools → New tool with Execution mode set to Client. Use
it when the action belongs in the participant’s browser rather than on a server — navigating your
app, prefilling a form, opening a record, or calling your own API as the user
who is already signed in.
The point of client mode is where the credentials live. The tool runs inside
your page, in the visitor’s existing session, so Waterr never holds your
users’ cookies, tokens, or API keys. Your handler is also the authorization
boundary: the agent can only invoke tools you registered, and your code decides
whether this particular visitor may run them.
The round trip
Handling a call
If you embed with the standard loader snippet, register one handler per tool name. That is the whole integration:Return a page name from an enum in your
parameters_schema, not a URL.
The model then picks from routes you actually have instead of inventing a
404, and no string from the conversation can be turned into a redirect.If you embed the iframe yourself
The loader adds?host=<your origin> to the iframe URL, and the widget
refuses to send tool calls to an unidentified origin — a tool call asks your
page to act, so broadcasting it to "*" is not an option. Hand-rolling the
iframe means adding that yourself and speaking the protocol directly:
call_id matching and error replies for you.
Timing and limits
Client-mode handlers run in a page, so they should return in well under a
second. If yours needs to do real work, do it asynchronously: return
{ status: "started" } immediately and let the persona ask again, or use
webhook mode instead.
Which embeds support this
Client-mode tools need a page listening for them, so they work on the sidebar widget only:
Attaching a client-mode tool to a scenario used on another surface is not an
error, but nothing will answer the call: the persona plays its filler line,
waits out
timeout_ms, and tells the participant the action did not
complete. If a scenario is reused across surfaces, keep its client-mode tools
on a variant that only the widget uses.
Example: navigating your site
Errors
Limits
API surface
Tools (account-scoped):
Scenario attachments:
Worked example — booking a meeting
Two tools (list_slots, book_slot) defined once, attached to both a sales
and a support scenario.
- User: “Can we set up a follow-up sometime this week?”
- LLM calls
list_slots(timezone="Asia/Kolkata"). - Your webhook returns
[{slot:"2026-06-20T10:00:00+05:30"}, …]. - LLM proposes the slots verbally.
- User: “Friday at 10 works.”
- LLM calls
book_slot(slot_iso="2026-06-20T10:00:00+05:30", title="Follow-up"). - Your webhook books it and returns
{ "ok": true, "booking_id": "bk_…" }. - LLM confirms: “Booked — you’ll get a calendar invite shortly.”

