Skip to main content
Bind a tool to an inbound webhook behind a verifier and a condition filter. The runtime fires a tool when a signed webhook arrives. A hook binds a topic to a tool; a verifier authenticates each delivery over its raw bytes before the payload is parsed; an optional condition filters which deliveries fire the tool. This guide covers the platform wiring — the hook, the verifier binding, and the filter. Provider-specific signature schemes live in that verifier plugin’s own repository README.
An event that belongs to an existing conversation goes through the event door, not a hook: it enters that thread as a turn on the conversation’s own FIFO, rather than firing a detached tool run with no thread. Use a hook when the event is standalone; use the event door when it must be ordered and answered inside a live conversation.

Register a hook

A hook names the topic to listen on and the tool to fire. Register it from a HookParams JSON body. Add a condition (a jq expression over the delivery) to fire the tool only on matching deliveries.
The name and topic must each be a single URL-safe path segment — ^[a-z0-9][a-z0-9_-]*$ (lowercase letters, digits, underscores, and hyphens, opening on a letter or digit). The topic becomes a segment of the public /universal_webhook/{topic} URL, so a dot, slash, uppercase letter, or space is rejected at registration with a 400. List the registered hooks:

Bind a verifier to the topic

An inbound webhook door is public — bind a verifier so every delivery on the topic is signature-verified before the tool fires. Name a registered verifier and pass its per-binding config. Verification fails closed: a signature failure rejects the delivery and the tool never runs.
The verifier config never holds a secret value — only the name of the environment variable that holds it, resolved at verify time. shared_secret also requires an id_header naming a header the sender fills with a unique per-delivery id; the ingress dedupes on it to refuse replays, so a binding without it fails closed.
1

Load the verifier

A verifier registers when the manifest imports its module. Load the built-in shared-secret verifier — or a provider verifier package — under lifecycle_modules.
manifest.yml
2

Register the hook

Bind the topic to the tool with tai hooks register.
3

Bind the verifier

Lock the topic’s door with tai hooks set-verifier. Remove a binding with tai hooks delete-verifier.
A topic with no verifier bound accepts unauthenticated deliveries — the door is open. Bind a verifier to any topic reachable from the internet. A body-signature verifier rejects GET delivery, so the raw payload cannot ride the URL unauthenticated.
A trigger link is a minted, public URL — GET /trigger/<token> — that resolves to a hook topic and fires its registered hooks, exactly as the ingress door does. The token in the path is the capability: whoever holds the URL fires the topic. Print the URL as a QR code — a picture of the URL — on a wall, a sticker, or a slide, and every scan fires the topic. A link is timed (you pick the lifetime in seconds; the store expires it) or permanent, and revocable by name at any moment. Trigger links belong to the hooks surface: any role granted hooks write mints and revokes them; hooks read lists them. Mint a timed link for a topic. Exactly one of --ttl SECONDS or --permanent is required — expiry is an explicit choice, with no default:
The command prints the absolute URL and the expiry. Present that URL as a QR; a scan is a bare GET, so a scan fires the topic:
Every miss answers the same 404 — an unknown, expired, or revoked token, or a topic that has since gained a verifier, are all indistinguishable, so the public door gives no oracle to probe. A successful response never names the topic either: the URL holder fires a topic without learning which one. Mint a link that never expires with --permanent, and name it so you can revoke it later:
Carry per-link tool arguments with --params (a JSON object). They are merged into every hook the link fires, so one hook can back many QRs, each with different inputs:
Revoke a link by name — immediate and durable:

What reaches the tool

Each fired hook runs its tool with a shallow, top-level merge of three sources, strongest last:
  1. the hook’s expr output over the scan payload (weakest — the scan influences it),
  2. the hook’s static tool_kwargs,
  3. the link’s --params (strongest — the most specific operator intent).
A scan’s query string reaches the tool only when the hook opts in with an expr that maps it. Register a hook whose expr lifts ?name= into the tool input:
Now GET /trigger/<token>?name=Ada fires notify with {"who":"Ada"} — unless the hook’s tool_kwargs or the link’s --params also set who, which win over the scanned value.
Treat the URL — and its QR — as a capability. Anyone who holds it fires the topic, for as long as the link lives. Revoke on any leak, and “leak” is broader than a copied link: the token rides the URL path, so it lands in reverse-proxy and server access logs — a log reader gains fire-capability. Automated URL fetchers leak it by firing it: chat and mail link-preview unfurlers and security scanners issue exactly the bare GET that fires the link, so pasting the URL into a chat can fire the topic before a human ever scans it. Mitigate with short --ttl and prompt revocation.
Verified topics and trigger links are mutually exclusive, both ways. Minting a link for a verifier-bound topic is refused, and a link fired against a topic that gained a verifier after minting answers the uniform 404. A trigger link is unauthenticated; routing it into a signature-verified topic would silently bypass that guarantee. If you need both, use two topics.
The token is shown once, in the create response. It is never stored and cannot be listed or re-read — a listed link’s QR is unrecoverable by design. To rotate a code, revoke the link and create a new one.
Revoking a link does not close the topic. The topic name itself stays firable through POST /universal_webhook/<topic> regardless of any link’s state — revocation kills the token, not the topic. Choose unguessable topic names for trigger-linked topics.
Link params reach every hook on the topic. The --params fan out per event to all hooks bound to the topic, including hooks registered later by anyone holding hooks write. If a link carries sensitive params, mind who holds hooks write on that topic.

Rate limits and merge behavior

The public /trigger/<token> door is rate-limited from the start — a public door must not ship without flood control. It is the trigger family, held at 60 requests a minute and 10 in any 10 seconds; over the limit answers 429. Flood control on public doors covers the budgets and how to tune them. The bucket is per client address. Two facts follow for a shared wall QR:
  • A crowd behind one NAT shares a single budget — the 11th scan in 10 seconds from behind that NAT gets a 429.
  • Behind a reverse proxy with no proxy trust declared (the default), every scanner collapses into the proxy’s single peer-address bucket. For crowd-scale deployments declare the proxy — TAI_RATE_LIMIT_TRUSTED_PROXIES (addresses or CIDR blocks) or TAI_RATE_LIMIT_TRUSTED_HOPS (a hop count) — and raise the trigger family’s budget.
Two more behaviors to know:
  • Params merge as a shallow, whole-value replace. A link’s flow_graph_kwargs replaces the hook’s entire flow_graph_kwargs value — there is no deep merge of nested keys.
  • Revocation survives a backup restore. A revoked link stays dead even after its record is restored from a backup, so restoring an old backup cannot re-arm a link you already revoked.
The Studio’s hooks page carries a trigger-links section: a table of live links (name, topic, expiry, a params indicator, and the token’s hash prefix) each with a revoke-confirm, plus a Create trigger link button. The create flow takes a topic, an optional name, an expiry (permanent, 1 hour, 1 day, 7 days, or custom seconds), and an optional params JSON. On success a dialog renders the QR code with a copy field for the URL and a shown-once caption — revoke and re-create to get a new code.
The Studio hooks page create-trigger-link dialog showing the generated QR code.The Studio hooks page create-trigger-link dialog showing the generated QR code.

Creating a trigger link on the Studio hooks page — the QR is shown once.

See also