Three fields on an agent config point at a tool that runs at a fixed moment rather than when the model chooses to call it:
Each is one tool reference or null. The tool has to be published before you attach it, exactly as an attached tool does, and publishing the agent pins it to the tool version live at that moment. A tool can be both attached and wired to a hook — it is one tool either way.

When each fires

on_enter

The hook runs to completion first, and the greeting waits for it. That is the point: the greeting resolves {{userdata.…}} against the live session bag the hook has just written to, so the opening line can be true about what the hook found.
The price is the hook’s own latency ahead of the agent’s first word. If you do not want the caller sitting through a CRM lookup in silence, open the hook’s tree with a say — it plays while the rest of the tree runs. Then exactly one opening line, or none:
  • The greeting, wherever there is one. An agent reached by a handoff runs its on_enter and then speaks its own greeting, like any other agent.
  • Failing that, and only on a handoff, a generated line: the caller has just asked for something and is waiting on this agent to answer it. On the first agent of a call the same silence is the “let the caller speak first” shape, and is left alone.
On text, on_enter fires when a cold conversation window opens — the first message, or the first after the previous window idled out. A warm window that is already open does not run it again. Text agents have no greeting, so nothing follows it. If the hook hands the call on or hangs up, the agent has no opening line left to speak and none is attempted.

on_exit

Fires when the session closes: the call ending, or a handoff away from this agent, on voice and video. On text it is end_call, the conversation window being closed, or roughly a minute of no messages. This is where an outcome gets logged, a lead gets posted, a CRM gets a note. A failure here never crashes the call — a failing POST leaves a log line, not a dropped caller.

on_user_turn_completed

Fires after every user turn and before the language model replies, with the user’s words available as {{args.user_message}}. On voice and video that is each spoken turn; on text, each inbound message. On text, {{args.user_message}} is the message text only. An image attached to the same message goes into the context before the hook runs, but it has no string form, so it is not in the argument. On an agent with screen share on, the newest frame of the shared screen is already in the context this hook runs against, so anything the hook adds to the context lands after it. The hook can stop the turn: ending the call or handing off from inside it suppresses this agent’s reply, and the target agent answers the turn instead. A hook that raises is logged and the turn continues.
A realtime agent cannot use on_user_turn_completed. A speech-to-speech model detects the end of a turn inside the provider’s socket, so the hook would never fire at all. Saving one is refused:

What a hook tool may not be

A hook tool cannot contain a transfer operation. A hook runs outside a turn, so the transfer’s wait for outstanding speech does nothing and the caller would be handed over mid-word. Checked at agent publish, because the same tool is perfectly valid attached normally:
A hook tool cannot read tool arguments it will not be given. At agent publish the hook’s operation tree is re-validated against the arguments that hook actually supplies: user_message for on_user_turn_completed, and nothing at all for on_enter and on_exit. So a tool whose tree reads {{args.account_id}} publishes fine on its own and is refused as an on_enter hook:
A hook has no model behind it to fill arguments in. Give it what it needs from {{userdata.…}}, {{vars.…}} or {{system_vars.…}} instead. transfer is the only operation a hook is refused. The ordinary ones behave as they do anywhere — http, code, if, say, add_message, set_variable, end_call and handoff all work. Publishing fields into userdata from a hook is the normal way to get a value in front of the agent before it speaks.

Worked examples

Load a CRM record on enter

The tool takes no arguments — a hook has nothing to give it — and identifies the caller from {{system_vars.human_phone_number}}, which is filled in by the platform.
on_error: "continue" is deliberate: a CRM that is down should leave a thinner greeting, not a failed call. The prompt then reads what the hook published:
Keep the greeting itself free of fields the lookup might not have found. A missing value resolves to nothing and the greeting collapses the leftover whitespace, but it does not fix up punctuation — "Hi {{userdata.name}}, thanks for calling" becomes "Hi , thanks for calling" when the CRM had no name. A prompt can be told to work around a blank; a greeting is spoken exactly as it resolves.

Log an outcome on exit

{{system_vars.now}} inside a tool is the moment the tool ran, so this timestamp is the end of the call rather than the start. In a prompt it would be frozen at the moment the agent picked up — see variables.

Flag a turn as it happens

{{args.user_message}} is the one argument this hook is given. Note the json_schema: a tool is validated against its own declared parameters when you publish the tool, so user_message has to be declared there or the tool itself will not publish. Then, at agent publish, the tree is checked again against what the hook actually supplies — and user_message is exactly that. Keep the timeout short. This runs between the caller finishing and the agent starting to answer, and every millisecond is silence on the line.
A hook is not the place for a slow job. on_enter delays the greeting, on_user_turn_completed delays every reply, and on_exit runs while the call is being torn down. If the work can take seconds, post it to something that can take its time.

Attaching one

A write takes the whole config, so read the current one and change it rather than sending these three fields alone. See agent overview.

Next

Operation tree

What a hook tool’s tree can contain.

Userdata

What a hook publishes, and who reads it afterwards.

Handoffs

How a target agent enters, and what runs when it does.

Versions

Publishing, and the tool version a hook is pinned to.