Skip to main content
Hooks let you plug custom logic into an agent’s runtime pipeline. At defined lifecycle events — when a message arrives, before/after the LLM call, when a file is ingested or about to be uploaded — a hook can inspect, modify, allow or block the payload by calling an external endpoint, a platform-provided automation, or by evaluating a declarative policy in the browser. Typical uses: PII redaction, toxicity / classification blocking, citation enforcement, file MIME or watermark checks, antivirus scanning, and the client-side file-classification gate (block a document before it ever leaves the browser).
Hooks exist at three levels — agent, org, and platform. They are merged (agent ∪ org ∪ platform). The agent UI manages the agent’s own hooks and shows org/platform hooks as read-only. An org/platform hook can be marked immutable so it always applies and cannot be disabled or overridden below.

How a hook works

For a server-side hook (http / workspace target) the platform sends your endpoint a JSON request describing the event, and your endpoint answers with a decision that the pipeline applies:
The two contracts that matter most are therefore what your hook receives and what your hook must answer — both documented in full below.

Lifecycle events

A hook subscribes to one or more events:

Targets

The target.type decides where the hook runs.

http — call your own endpoint

The hook POSTs the event payload to a URL and uses the response to allow / modify / block. The endpoint is most often a Builder automation exposed as a webhook, so you stay inside Prisme.ai (RBAC, secrets, audit) — this is the recommended way to run your own hook logic.
The request carries only Content-Type: application/json (plus the platform’s own x-prismeai-workspace-id). Custom request headers and per-request auth are not forwarded — put any secret the endpoint needs in the workspace itself (Builder secrets / config), or in a non-guessable path segment of the URL.

workspace — call an automation by slug

Reference a Builder automation by workspace_slug + automation_slug instead of a full URL — the platform builds the webhook call for you and sends the same { event, content, callback?, options? } body as http. The target automation must declare when: { endpoint: true }.
target.options (optional) is forwarded verbatim so a single automation can serve several hooks with different static config — read it as {{body.options}}.
workspace targets fail closed: if the target returns HTTP ≥ 400, or a body with no decision (e.g. the automation doesn’t exist or is missing endpoint: true), the hook resolves to an error and your on_error policy applies — so on_error: "deny" really does block. A handful of platform test slugs (_mock-hook-*) are dispatched internally rather than over HTTP.

client — declarative browser policy (file gate)

A client hook is evaluated in the browser against a file’s extracted metadata, before any upload. No external call, no third-party JavaScript — see The client-side file gate. For client targets mode is ignored (it is always a synchronous, in-browser check) and the only valid event is before_upload.

Execution modes

mode controls the server-side timing of http / workspace hooks. It is ignored for client targets.
async and stream are advanced modes with a callback contract — see Async & stream callbacks. sync is what the vast majority of hooks use.

What your hook receives

Every server-side hook is a POST with this body:
When the target is a Builder automation, the whole POST body is available as {{body}} — so you read {{body.event}}, {{body.content.text}}, {{body.callback.token}}, etc.
The content object depends on the event:
For after_file_parse, large documents (over ~256 KB of text) are sent by reference: content.payload_mode is "reference" and you get a content.signed_url to fetch instead of inline content.text. Handle both — a hook that only reads content.text will see nothing on large files.

What your hook must answer

A sync hook must return a decision object:
score and tags are optional — they are recorded on the hook.invoked analytics event but do not by themselves change the outcome.

Decision precedence

When several hooks fire on the same event, their decisions are merged with this precedence:
A single deny blocks the payload regardless of what the other hooks returned.

Signalling an error

If your endpoint cannot make a decision, return an object with an error and no decision:
The platform then applies the hook’s on_error policy (allow = fail-open, deny = fail-closed). A thrown HTTP error, a non-JSON response, or a timeout is treated the same way.

Examples

A before_message PII gate that blocks or redacts:
An after_llm guard that enforces citations:

Behavior controls

Choose on_error deliberately. For a security control (block forbidden files/content) use on_error: "deny" so a failing or unreachable hook never silently lets the payload through. For a best-effort enrichment use allow.

Error handling & resilience

Hooks are designed to never take the agent down:
  • on_error policy — every failure (timeout, error response, { "error": ... } body) resolves to allow or deny per the hook’s setting.
  • Circuit breaker — if a hook fails repeatedly (5 failures within 60s) it is temporarily skipped for ~60s and its on_error policy applies to skipped calls. It resets on the next success.
  • Rate limiting — beyond rate_limit_per_min, further calls are skipped (again honouring on_error).
Every invocation emits a hook.invoked event (with decision, duration_ms, score, tags, error) that you can inspect in the activity feed / analytics.

Async & stream callbacks

async and stream hooks respond in two phases: an immediate acknowledgement, then a callback into the task. The request body carries a callback object:
Authenticate every callback with the token in an X-Hook-Token header (an Authorization: Bearer <token> header is also accepted).

async (before_file_ingest)

Acknowledge immediately, then finish the task when your scan completes:
The token defaults to a 24h TTL for async.

stream (before_llm)

Your endpoint takes over the LLM step and pushes events to callback.url:
The agent waits for the first event (default ~2s first-byte timeout) up to a total ~60s, then uses the completed output as the LLM response. The stream token defaults to a 5-minute TTL.

The client-side file gate

The most common client hook is a file-classification gate: read a document’s metadata locally and reject non-conformant files before they ever leave the browser (e.g. a document classified above the level your SaaS is allowed to process).

Policy rules

Rules are evaluated with AND semantics — the first rule the file does not satisfy whose on_fail is deny blocks the upload.
in / equals / matches / exists are fail-closed: a missing property fails the rule, so an unmarked document is blocked by a deny rule. not_in is fail-open (nothing to forbid when the property is absent) — pair it with an exists rule to also block unmarked files.

Metadata namespaces

The extractor flattens each file’s metadata into namespaced keys you target with property:

Microsoft Purview / MIP sensitivity labels

MIP stores its label differently depending on the Office version and the file type — in the docMetadata/LabelInfo.xml part (modern Office), as MSIP_Label_<guid>_* custom properties (legacy Office, PDF via Acrobat), or packed into a single msip_labels header (Outlook .eml). The extractor normalizes all of them into the same flat keys, which also match what the server-side crawler (Tika) emits — so one rule works everywhere: A typical gate — only documents carrying one of the approved labels may be uploaded:
The label GUID is the id of the label in your Microsoft Purview compliance portal; you can also read it from a labeled document (unzip -p doc.docx docMetadata/LabelInfo.xml) or from the crawler’s extracted metadata (mip:label:id). Removed (removed="1") and disabled labels are ignored.
Files whose metadata cannot be read in the browser — label-encrypted Office files (the label encrypts the container), Outlook .msg, .pfile — yield an empty metadata bag, so a fail-closed deny rule blocks them. That is the safe default: a document that cannot prove conformance does not go through.
The client gate is a fast-fail UX convenience, not an authoritative barrier — it runs in the user’s browser. Always pair a classification policy with a server-side before_file_ingest hook (target: http or workspace) that re-checks the file, so the rule is enforced even if the browser is bypassed.

Org & platform hooks

Hooks defined at the org or platform level apply to every agent in scope. Set immutable: true to make a policy mandatory: it always applies and cannot be disabled or overridden by an agent. In the agent UI these appear as read-only / locked, with locked_by indicating org or platform. Only org/platform configs may set immutable — an agent-level hook that tries to is rejected.

Limits

  • Up to 10 hooks per agent.
  • Unique id per hook; each hook must declare at least one event.
  • Default rate limit: 1000 invocations/minute per hook.
  • http targets must use https:// and may not point at internal/private addresses.

Configuring hooks

Hooks are managed from the agent’s Capabilities page (see Capabilities). Add a hook, pick its event(s), choose the target (HTTP / workspace / client) and mode, then set the behavior controls. For HTTP targets, point it at a Builder automation exposed as a webhook and implement the request / response contract above.