Skip to main content
This page applies to both LLM tools and Perception tools. Every tool dispatches via exactly one of two channels, picked per tool via the delivery field:
  • App message (default) - Tavus emits a conversation.tool_call event (or conversation.perception_tool_call for perception tools) on the data channel; your frontend handles it.
  • API call - Tavus makes an HTTPS request to a URL you configure, either a customer-controlled callback or a third-party API directly. Auth + body shape is configured per tool.
A tool with delivery.app_message: true and a delivery.api block at the same time is rejected. Setting both to disabled is also rejected. Pick exactly one.

App message delivery

The default. Your client receives the tool call inline with the conversation events.
App message delivery (default)
Each invocation arrives as a conversation.tool_call event carrying a tool_call_id. To return a result, send a conversation.tool_result event back with the matching tool_call_id - that’s how Tavus pairs the result with the in-flight dispatch and applies the configured on_resolve.
conversation.tool_result (sent by your client)
If your client never sends a result, the dispatch eventually drops out of context. There’s no hard timeout on this path - the PAL just won’t have the data.

API delivery

Tavus calls an HTTPS endpoint each time the tool fires. The exact request shape depends on whether you’re hitting a third-party API directly (any auth.type other than hmac) or a customer-controlled callback (auth.type: hmac).
API delivery

delivery.api fields

URL templating

{placeholders} in the URL path or query string are filled at request time from the LLM’s tool arguments and URL-encoded. The hostname must be static.

Reserved system placeholders

In addition to the LLM’s tool arguments, Tavus injects a small set of system placeholders at request time. They’re available everywhere placeholders are interpolated - URL, query_params values, and body_template. Useful for idempotency keys, correlation IDs in your logs, and Tavus-side tracing.
The tavus_ prefix is reserved. Tool parameters.properties cannot declare a property whose name starts with tavus_ - the create / update tool API rejects it with 400. Pick a different name (e.g. customer_conversation_id) if you need a similar field.

Request body and query string

Only arguments declared in your tool’s parameters.properties ride along. Body and query string are decided independently - you can combine them in any way.

Body

The HTTP method decides whether a body is sent. GET / HEAD / DELETE never carry a body - body_template on those methods is rejected at validation time.

Query string

query_params applies to every method - you can attach a query string to a POST as easily as to a GET.

Example - reshape flat tool args into a nested API body

Suppose the tool’s parameters schema defines two flat fields:
Tool parameters (what the LLM emits)
But the API expects a nested shape - query.text and filters.region. Use body_template to remap:
delivery.api with body_template
When the LLM calls the tool with { "search_term": "pizza", "region": "tokyo" }, Tavus sends:
body_template is a regular JSON object - write it as one, no escaping. Each {placeholder} inside a string value is replaced with the matching LLM argument. If a string is exactly one placeholder ("{count}"), the substituted value preserves its native type - so "{count}" with count: 10 produces 10 (number), not "10" (string). Non-string values in the template (numbers, bools, null) pass through unchanged.

Tavus callback shape

Used when auth.type: hmac is set. Tavus ignores body_template / query_params / URL templating in this mode and sends a fixed JSON envelope, signed with HMAC-SHA256.
Request body (POST/PUT/PATCH)
Request headers Tavus always sets in this mode: Any extra delivery.api.headers you configured are merged in.

Response Tavus expects

The same response handling applies to both API modes: When the status is error or timeout and on_resolve is not fire_and_forget, the PAL acknowledges the failure to the user (no result content is fed back to the LLM).

Verifying API signatures

When you set auth.type: "hmac" with an auth.secret, Tavus signs the exact request body bytes with HMAC-SHA256 and sends the hex digest in X-Tavus-Signature. Verify it before trusting the payload.
The signing input is the raw body Tavus sends, which is canonical JSON with keys sorted alphabetically. Verify against the raw bytes you received, not a re-serialized version - any re-encoding can change byte order and break the signature.
Python (Flask)
Node.js (Express)
Replace <api-key> with your actual API key. You can generate one in the PAL Maker.