For the complete documentation index, see llms.txt. This page is also available as Markdown.

Tools & Events

Let your Human Agent call functions during a conversation, and react to the agent from your own page.

Tool Calling

Tools (function calling) let the agent's LLM trigger actions while it talks: look something up, submit a form, update your UI, or notify a backend system. You define each tool's name, description, and JSON-schema parameters; the LLM decides when to call it and with what arguments.

Tools are configured per agent. You can manage them two ways:

Each tool has an enabled flag. A disabled tool keeps its configuration but is hidden from the LLM, so you can turn a tool off without losing its setup.

Limits worth knowing before you design a tool:

  • The agent executes at most 3 tool calls per model response. Further calls in the same response are dropped, and the model is told to answer with speech instead.

  • Back-to-back identical calls (same tool, same arguments) inside one turn are collapsed to a single delivery. A tool meant to be called repeatedly with the same arguments should vary an argument.

  • end_call is reserved for the built-in hang-up tool. Do not use it as a custom tool name.

  • Only properties and required are read from your parameters object. Every other top-level JSON Schema keyword is accepted by the API and then dropped, including type (even type: object), additionalProperties, $defs, oneOf, anyOf, allOf and a top-level description. Express constraints inside each property rather than at the top level.

Delivery Modes

Every tool declares how its calls are delivered. There is no agent-wide default. Each tool picks its own mode.

Mode
Where the call goes
Your responsibility

Webhook

Ojin POSTs the call to a URL you configure, server-side

Stand up the endpoint; no browser code

Client

The widget emits a DOM event in the user's browser

Listen for the event on the embedding page

Use webhook delivery for backend actions (write to a database, call an API, send a notification). Use client delivery for things that happen in the user's browser (navigate the page, open a modal, update on-page state).

Webhook Delivery

When a webhook tool fires, Ojin sends an HTTP request to the tool's configured URL:

  • Method POST, header Content-Type: application/json.

  • A 30-second timeout.

  • Redirects are not followed. Point the tool at the final URL: a 301 or 302 reaches the model as an error.

  • The URL must be http or https and must resolve to a public address. Loopback and private addresses (localhost, 127.0.0.1, 10.x, 192.168.x, 169.254.x, ::1) are rejected, and a tool with a rejected URL is dropped for that session. You cannot point a webhook at your laptop; use a tunnel with a public hostname.

The request body carries the call under a tool_call_message object. The fields you'll use:

Field
Meaning

name

The tool that was called

parameters

The call arguments, as a JSON string (parse it before use)

tool_call_id

Unique id for this call

response_required

true when the agent is waiting for your response (see below)

The body also includes additional session metadata fields alongside tool_call_message; you can ignore them for most integrations.

Waiting for a response

Each webhook tool has a wait for response setting:

  • On (response_required: true), the agent waits for your endpoint, then feeds the result back into the conversation so the LLM can use it. The result is always added to the conversation, but the agent only speaks about it in the same turn if it had not already started speaking. Otherwise the model uses it from the next turn onward. Return HTTP 2xx with a JSON body:

    If you omit the result key, the entire JSON body is used as the result. An empty body (or 204) is treated as an empty result. A non-2xx status, a timeout, or an unparseable body surfaces an error result to the LLM instead.

  • Off (response_required: false), fire-and-forget. Ojin sends the request and does not wait; the LLM immediately continues with a neutral acknowledgement. Anything your endpoint returns is ignored.

Client Delivery

When a client tool fires, the widget dispatches a DOM CustomEvent named ojinToolCall. The event fires on window (not on the <ojin-agent> element), so add the listener to window:

The arguments field is renamed to args above because arguments is a reserved word inside regular functions.

event.detail carries:

Field
Type
Meaning

function_name

string

The tool that was called

tool_call_id

string

Unique id for this call

arguments

object

The call arguments, already parsed

Client delivery is one-way: the agent does not wait for your page, and there is no tool-result channel back from the browser. If the LLM needs to use a result, use webhook delivery with wait for response turned on. To feed information back for later turns, the widget exposes appendSystemContext(), which works on the default speech pipeline.

Managing Tools via the REST API

Agent endpoints accept your API key, so you can manage tools programmatically. Authenticate with the X-API-Key header (see Get your API key).

Tools live on the agent itself. Set them with the agent update endpoint, PATCH /v1/agents/{agent_id}, using the tools array:

To read an agent's current tools (each returned with its server-assigned tool_id), use GET /v1/agents/{agent_id}. For the full schema, see the REST API reference.

The API enforces these limits on each tool:

Field
Constraint

function.name

Max 64 characters, ^[a-zA-Z0-9_-]+$, unique per agent

function.description

Max 1024 characters

delivery.url

Max 2048 characters, must be a valid URL

Widget Events

Beyond ojinToolCall, the <ojin-agent> widget emits DOM events you can listen for to build custom UI, transcripts, or telemetry around the conversation.

All of them are observable on window, but they get there two different ways. ojinToolCall, LatencyReport, ConnectionTimingReport and OjinSessionOutcome are dispatched on window directly. The rest are dispatched on the <ojin-agent> element and bubble up to window.

Conversation events

Event

detail

Fires when

ojinToolCall

{ function_name, tool_call_id, arguments }

A client-delivered tool is called (see above)

botIsSpeaking

{ isSpeaking: boolean }

The agent starts or stops speaking

ReceivedTranscriptTurn

{ owner: "user" | "bot", text, final: true }

One finished conversational turn. This is the event to use for a transcript: one clean, de-duplicated line per turn

ReceivedTranscriptUser

{ text, timestamp, user, final }

A user speech transcript arrives (final: false for interim)

ReceivedTranscriptBot

{ text }

A chunk of the agent's response text arrives. Raw deltas, not a finished line

ReceivedTranscriptBotPartial

{ owner: "bot", text, final: false }

The cumulative text produced by the speech service for the agent's current turn updates

BotTtsStopped

no payload (detail is null)

The agent's current spoken turn ends or is interrupted

UserStartedSpeaking

{ instanceId }

The user starts speaking

UserStoppedSpeaking

{ instanceId }

The user stops speaking

ConnectionTimeout

{ duration_s, bot_state, connection_state }

Connection setup exceeds its timeout; the widget resets and shows an error

UpdateBotState

{ botState, instanceId }

The agent's lifecycle state changes

UpdateConnectionState

{ connectionState }

The transport connection state changes

UpdateQueuing

{ queuing: boolean }

The session enters or leaves the waiting queue

Where a payload includes instanceId, it identifies the <ojin-agent> element the event came from, so a page hosting several widgets can tell them apart.

Advanced / telemetry events

These carry fine-grained audio and timing signals, useful for meters, latency dashboards, or debugging. They are more granular than the conversation events above and may change. Don't depend on them for core logic.

Event

detail

Fires when

userSpeakingChanged

{ isSpeaking: boolean }

Browser voice-activity detection toggles

userVolume

{ volume: number }

The user's microphone level is sampled

botVolume

{ volume: number }

The agent's playback level is sampled

LatencyReport

per-utterance latency breakdown

The agent reports timing for a turn

ConnectionTimingReport

connection timing breakdown

The session finishes connecting

OjinSessionOutcome

{ outcome, phase, faultDomain, sessionId, agentId, instanceId, httpStatus, elapsedMs, widgetVersion }

A connection attempt or a live session ends. See below

OjinSessionOutcome

Fires once when a connection attempt finishes and again when a live session ends, so you can measure how often visitors reach a conversation and why they don't. phase is connect or session; outcome is a value such as success, mic_permission_denied, agent_unavailable, transport_lost or ended_by_visitor. faultDomain coarsely attributes it, client_env, ojin, backend, or unknown when the outcome carries no attribution (every normal ending does).

instanceId identifies which <ojin-agent> element it came from, since the event fires on window and a page may host several. sessionId is present only once a session exists, a microphone refused before the session starts has none.

Treat the outcome vocabulary as open: new values are added as new failure modes are identified, so match on the ones you care about rather than assuming the list is closed.

Next Steps

Last updated

Was this helpful?