Skip to main content
TurnCall agents can call tools during conversations. Tools let the AI take actions — transfer calls, look up customer data, book appointments, and more.

Built-in Tools

These work out of the box with no server needed:

Opt-outs (do_not_call)

Opt-in: an agent has it only if its tools list names it. When a caller asks not to be called again, the tool suppresses their number for the whole project — the dialling number on an inbound call, the dialled one outbound — so every later outbound dial to it is refused 409 number_suppressed. It then records call.opted_out ({number}, also sent to webhook subscribers) and ends the call. A call with no phone number (WebRTC) is refused and keeps running.

Ending a call politely

end_call and do_not_call don’t hang up when they run. The model answers the tool’s result as it would any other — that answer is its goodbye — and the call ends once that reply has played in full. A model that already said goodbye alongside the tool call is told it may reply with nothing. The goodbye is final. From the moment it is written the call stops listening: the caller cannot interrupt it, and anything they say while it plays is not transcribed or recorded. So the tool belongs at the moment the conversation is over, and the goodbye after it. Many models will not say goodbye and call a tool in one reply; if the instructions ask for the goodbye first, they say it, wait for the caller, and only call the tool on the caller’s next turn. A description that works: “Call this when the caller is done or says goodbye. You will then say a short goodbye, and the call ends after it.” POST /v1/calls/{call_id}/end is unchanged: an operator ending a call from outside hangs up at once.

Call transfer (cold / warm)

transfer_call moves a live Twilio call to a human:
  • transfer_mode: cold (blind — bridge immediately) or warm (brief the operator first).
  • transfer_message: spoken to the caller before the dial (“Connecting you…”).
  • briefing (warm only): spoken to the operator before bridging — a string, or {"from_summary": true} to summarize the transcript on the fly.
  • fallback_message: spoken to the caller if the operator doesn’t answer, then the call ends.
The same parameters work via the control API: POST /v1/calls/{call_id}/transfer. Warm transfer and fallback_message require PUBLIC_BASE_URL to be set (Twilio calls back to TurnCall for the operator briefing and the no-answer fallback). If the operator’s line goes to voicemail, the caller is still connected (and can leave a message); a transfer.answered event reports answered_by (human/machine). See the the examples/call-transfer example.
Transfers redirect the call’s PSTN leg, so they work on Twilio calls — a WebRTC or WhatsApp session has no phone leg to bridge.

Choosing the transfer target

target_number is an argument the LLM supplies, so where the number comes from is a design choice. Rule of thumb: prompt = policy (when to transfer, what to say), code = facts that change (who’s on call, at what number). For the lookup-tool pattern, three rules keep transfers reliable: always return a number (bake in a fallback line — an error mid-transfer strands a frustrated caller), answer fast (the caller waits in silence; the default tool timeout is 10s), and return E.164. Avoid putting schedules or rota tables in the prompt itself — the LLM has no reliable clock and can mistranscribe digits; that logic belongs in the tool.

Custom Webhook Tools

Define tools with a webhook_url — TurnCall POSTs to your server when the LLM invokes the tool:

Sync and async tools

execution_mode decides what happens when the caller interrupts while a tool is still running. Use async for work that is slower than the conversation — a CRM lookup, an availability check against a slow system — so a caller saying “actually, make that Tuesday” doesn’t throw away a request already in flight.
Voice only. A text turn is request/response: there is no ongoing conversation to deliver a late result into, so execution_mode is ignored on SMS, the Chat API and WhatsApp text.

Webhook Payload

Your server receives:
Return any JSON — it’s passed back to the LLM as the tool result. Exactly one of call_id and session_id is set. A voice call sends call_id; an SMS, Chat API or WhatsApp text conversation sends session_id and leaves call_id null. Branch on whichever you need — or ignore both if your handler is stateless.

Tools on text channels

The same webhook tools work in SMS, the Chat API and WhatsApp text. Two differences worth knowing:
  • Built-in tools are voice-only. end_call, transfer_call, send_dtmf, do_not_call and handoff_to_agent all act on a live call, so they aren’t offered in a text session. Configure them if you like — they’re simply not advertised to the model there.
  • Tool exchanges aren’t part of the stored history. Chat history is rebuilt from the stored customer and assistant messages each turn, so the model sees its own tool calls while it resolves one message and not afterwards.
A text turn runs at most five rounds of tool calls. On the fifth, the tools are withheld and the model is asked to answer in words, so a model that keeps calling tools still produces a reply instead of hanging the conversation.

Signed Tool Webhooks

Set webhook_secret (min 16 chars) on a tool and TurnCall HMAC-signs every POST so your endpoint can verify it really came from TurnCall:
Each request then carries X-TurnCall-Signature: v1=<hex> and X-TurnCall-Timestamp — HMAC-SHA256 over "{timestamp}.{raw_body}", the same scheme as event webhooks. Verify with:
Unset secret = unsigned POST (backward compatible).

Tool Invocation Recording

All tool calls (webhook + MCP + built-in) are recorded in the tool_invocations table with:
  • Input arguments
  • Output result
  • Status (success/error)
  • Latency (ms)
Query invocations via the API:
Text sessions (SMS, Chat API, WhatsApp text) are recorded the same way, under their session id instead of a call id:
Every row carries both call_id and session_id, exactly one of them set.
Recording is best-effort and runs after the answer goes out, so a database hiccup costs you the audit row rather than the caller’s reply. Each recorded call also dispatches a tool.result webhook event, on both voice and text.

Validate Tool Schema

Test your tool definition before adding it to an agent: