Skip to main content
A hook binds an inbound event topic to a tool. When an external system delivers an event on a topic, the hooks manager fires every hook registered for that topic whose condition matches the payload, running each bound tool in the background. This page explains the model. To register one, use the Studio’s Hooks screen or follow Fire a tool from a webhook.

The dispatch model

A hook is a small record: a topic, the tool to run, the keyword arguments to pass it, and an optional condition and expression over the payload. Registration is keyed by topic, so one topic can drive several hooks and the manager dispatches to all of them:
The condition is what makes dispatch selective — a hook fires only when its condition matches the event payload, so one topic can route different events to different tools. condition and expr are jq, and they say so in the schema: both carry the x-tai42-expression annotation, which names the input document and what the result is consumed as. An editor reads that annotation and offers the visual jq editor on the field without being told which fields are expressions.

Naming a hook and topic

A hook’s topic is dispatched as one path segment of the public webhook URL (/universal_webhook/{topic}), and its name addresses the hook on every management and delete door. Both must be a single URL-safe path segment: match ^[a-z0-9][a-z0-9_-]*$ — begin with a lowercase letter or digit, then lowercase letters, digits, underscores, and hyphens only. No dots, slashes, uppercase, or whitespace — a / or a stray character would mint a record no delivery could route to and no door could address. The value is never sanitized: a violation is rejected at registration with a 400, its message naming the offending field and the pattern.

The ingress door

External systems deliver events to the public POST /universal_webhook/{topic} door. The payload parser accepts any content type, and the matched hooks run in the background — the ingress returns without waiting for the tools to finish.
/universal_webhook/{topic} is the most exposed door on a server. A topic with no verifier binding is open by design; bind a webhook verifier to a topic to authenticate every inbound event over its raw body before it is parsed and dispatched. The public-door flood limiter throttles the door regardless.

Targeting a state subject

A hook may carry an optional subject so a state write during the fire is keyed and attributed to the hook. The subject names a conversation-target scope, a subject kind, and a key_expr — a jq expression evaluated over the event payload at fire, which must yield a non-empty string:
The fire runs with this subject as its ambient state context: a state tool the hook calls resolves the subject from it, and the write is recorded under door hook with the hook’s execution key as the actor. A key_expr that yields nothing fails the fire loudly, like any hook error. Leaving subject unset leaves the fire with no ambient state context, so a state tool it calls must carry an explicit subject. The Studio’s Hooks screen surfaces the subject as an optional group on the register form.

Managing hooks live

Hooks are managed at run time, with no restart. The manager comes in an in-memory and a Redis-backed form, and the authed management doors (GET/POST/DELETE /api/hooks) are a thin skin over it — the same manager the hook-management operations drive. Register and remove hooks from the CLI:
register takes the whole hook record — topic, tool, kwargs, and an optional condition — as one --params JSON object; its full option set is on the command itself:
examples/hooks/register_hook.sh
Setting or removing a topic’s verifier binding is a separate authed door (PUT/DELETE /api/hooks/topics/{topic}/verifier). See the fire-a-tool-from-a-webhook guide for the binding-and-condition workflow, triggers and webhook verifiers for authenticating the ingress, and the HTTP API reference for the hook routes.