Firing a tool from a webhook
The webhook path reuses two existing pillars. An external system delivers an event to the public/universal_webhook/{topic} door; a hook bound to
that topic then runs its tool in the background. So “fire a tool from a webhook” is:
map a topic to a tool with a hook, and point the provider’s webhook at that topic.
Because the topic rides the webhook URL as a single path segment, a hook’s name
and topic must each be one URL-safe segment — ^[a-z0-9][a-z0-9_-]*$ (lowercase
letters, digits, underscores, and hyphens, opening on a letter or digit; no dots or
slashes). Registering a hook with a topic or name outside that charset is rejected
with a 400.
A hook fires a detached tool run. An event that belongs to an existing
conversation goes through the event
door instead, so it enters that thread
as a turn on the conversation’s own FIFO rather than as a standalone run.
Webhook verifiers
The public webhook doors —/universal_webhook/{topic} and the
interactions callback — authenticate an inbound request
over its raw bytes, before parsing, through a named verifier resolved from the
webhook-verifier registry. A topic with no verifier binding is open by design; you
bind a verifier to lock it.
Verification fails closed: a signature mismatch returns a constant 401, and a
missing verifier or secret returns a 500 — either way nothing is recorded and no
tool runs. A verified delivery is then deduped against replay: on the
/universal_webhook/{topic} ingress a delivery whose per-delivery id was already
claimed within the replay window is a replay, answered with the idempotent
already_seen 200 and dispatching nothing.
The built-in shared-secret verifier
The skeleton ships ashared_secret verifier that checks a named request header
against a secret read from an environment variable, with a constant-time compare.
A static secret carries no freshness, so its config also requires an id_header
naming a header the sender fills with a unique per-delivery id, which the ingress
dedupes on to refuse replays. Its config names the header, the env var holding the
secret, and that id header:
config also takes an optional replay_window_seconds (a positive int, default 86400
— one day) setting how long a delivery id is remembered before the same id would be
accepted again.
The secret is referenced by env var name, never embedded — the binding carries
no credential. Provider-specific verifiers (for example a signature scheme for a
particular SaaS) ship as their own plugin packages
and register the same way.
Scheduled triggers
The other trigger is time. A backend schedule fires a tool on a cadence or at a later time, naming the tool and its arguments — no external event required. A schedule may carry an optional subject in its tool arguments so a state write during the fire is keyed and attributed to the job. Unlike a hook’s key expression, a schedule’s subject is a literal{target_kind, target_name, kind, key} — the fire is anonymous, so nothing is
resolved from a payload. A malformed subject is refused when the schedule is
created, so a job that could never resolve its subject is never persisted. The fire
records the write under door schedule with no actor. The Studio’s schedule dialog
surfaces the subject as an optional group.
Every webhook door is throttled by the always-on public-door flood
limiter, so an open door
never ships without flood control.

