> ## Documentation Index
> Fetch the complete documentation index at: https://agentheya.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Fire HTTP requests when conversation events occur — and fully control what happens next.

<Info>
  **Field reference:** [`hooks`](/reference/hooks) — every field, type, default, and tier for this page.
</Info>

Hooks let you intercept and control agent behaviour in real time. When an event fires, Agentheya sends an HTTP POST to a URL you configure. Blocking hooks can halt the request, rewrite the prompt, replace tool results, inject messages, or surface notifications to the user. Non-blocking hooks are fire-and-forget for logging and side-effects.

## Creating a hook

1. Open your agent and go to **Hooks** in the sidebar (under **Advanced**).
2. Click **Add hook**.
3. Fill in a **Name**, pick an **Event**, set the target **URL**, and choose **Mode** (`Non-blocking` or `Blocking`).
4. Optionally set a **Timeout (ms)** (for blocking hooks), add **Custom headers**, and a **Signing secret**.
5. Save.

Use the **ON / OFF** toggle on each card to enable or disable a hook (this auto-saves). Multiple hooks can target the same event.

Click **Show hook protocol** at the top of the page to see the full request/response contract inline.

## Events

| Event                  | When it fires                                                                                                      | Blocking?    |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------ |
| **On user message**    | When an incoming message is received, before context is built                                                      | Yes          |
| **Before query**       | After context is assembled, before the LLM call                                                                    | Yes          |
| **After query**        | After the agent produces a response                                                                                | Configurable |
| **On error**           | When the LLM or engine fails                                                                                       | No           |
| **Before tool call**   | Before any tool is invoked                                                                                         | Yes          |
| **After tool call**    | After a tool returns                                                                                               | Configurable |
| **On inbound message** | When an inbound email or IM message arrives, before the agent replies — the `source` field identifies the channel  | Yes          |
| **On handoff**         | When the conversation is handed off to a human or sibling agent (handoff tool, auto-escalation, or owner takeover) | No           |
| **Handoff resolved**   | When a human or sibling agent hands the conversation back to the bot, with any structured return values            | No           |
| **On alert**           | When a monitoring alert rule triggers                                                                              | No           |
| **Goal complete**      | When the agent marks a Purpose success-criteria goal complete (`mark_goal_complete`)                               | No           |
| **On payment**         | When a customer pays for access via Stripe (`payment_event` = completed / renewed / payment\_failed / cancelled)   | No           |
| **On rate limit**      | When a request is blocked by quota enforcement                                                                     | No           |
| **Conversation start** | When a new session is created                                                                                      | No           |
| **Conversation end**   | When a session ends                                                                                                | No           |
| **Scheduled run**      | After a scheduled run completes, with its output                                                                   | No           |
| **Feed match**         | When a feed monitor reacts to a new item, with the item and the agent's response                                   | No           |

**Blocking hooks** wait for your endpoint before the agent continues. They can halt, rewrite, or replace any part of the request — see the response protocol below.

**Non-blocking hooks** fire in the background and the agent never waits for a response.

<Info>
  **Reacting to inbound email/IM.** The most-requested hook is **On inbound message** — it fires the moment an email or IM message reaches the agent, before any reply is generated. As a blocking hook it can drop the message or suppress the reply entirely; as non-blocking it gives you a real-time feed of everything arriving on your channels.
</Info>

<Note>
  **Hooks are one stage of the event pipeline.** Two sibling features handle inbound events (email, webhooks, feeds, IM, MCP) earlier and locally, without an external endpoint:

  * **[Event pre-processor](/sidebar-menu/event-pre-processor)** — your own code in a sandbox (Python 3.11 / Node 20) that runs *before* any hook, the LLM classifier, and the agent, returning a verdict (`pass_to_agent` / `drop` / `store` / `run_tool` / `respond`). It's the cheapest place to filter bulk mail or health-check pings, because a `drop` here skips the classifier entirely — and it fails open, so an event is never silently lost.
  * **[Interpreters](/sidebar-menu/event-interpreters)** — named match rules (JSON field presence or regex) that prepend a plain-language hint telling the agent *what an event is* before it reads the body, when one ingress receives several different payload types.

  Reach for these when the logic can run inline; use a Hook when you need an external HTTP endpoint in the loop.
</Note>

## Examples — from simple to advanced

**Hello world — log every conversation.** Add a **Conversation start** hook, mode **Non-blocking**, pointing at your logging endpoint. Every new session POSTs to your URL with the session metadata. The agent never waits; you get a real-time feed of conversations into your own systems. No response protocol needed — fire and forget.

**Simple — Slack notification on errors.** Add an **On error** hook (non-blocking) pointing at a Slack incoming-webhook relay. Whenever the engine fails, you get a Slack message. Pair with an **On rate limit** hook to know when quota is biting.

**Medium — moderate incoming messages.** Add an **On user message** hook, mode **Blocking**. Your endpoint inspects the message and returns the response protocol: allow it through, or **halt** with a polite refusal if it violates your policy. This is a content gate that runs before the agent ever sees the message. See [Response protocol](#response-protocol).

**Complex — inject live context before the LLM call.** Add a **Before query** hook (blocking) that looks up the user in your CRM and returns extra system context (their account tier, open tickets) for the agent to use — so every reply is personalised with data the agent didn't have to ask for. See [Full prompt control (pre\_query)](#full-prompt-control-pre_query).

**Complex — gate and rewrite tool calls.** Add a **Before tool call** hook (blocking) that intercepts a `send_email` call, checks the recipient against an allow-list, and either lets it proceed, rewrites the arguments, or blocks it. This is policy enforcement at the tool boundary — the agent proposes, your hook disposes. See [Tool interception (pre\_tool\_call)](#tool-interception-pre_tool_call).

**Scope a tool-call hook to one tool.** On a **Before tool call** hook, set **Tool name pattern** to `^send_email$` (or click **Select tools** and pick it from the list) so the hook fires only for that tool instead of every tool call. See [Advanced options → Tool filter](#advanced-options).

**Page only on critical alerts.** On an **On alert** hook, set **Minimum severity** to `critical` so you are notified only for the most severe monitoring alerts, not for every `info` or `warning`. See [Advanced options → Alert filter](#advanced-options).

**System-pushing — a full control plane.** Combine blocking hooks across the lifecycle: **On user message** to moderate, **Before query** to inject context, **Before tool call** to enforce tool policy, **After tool call** to redact sensitive fields from results, and **After query** to run a final compliance check that can replace the response before it reaches the user — all signed with HMAC so your endpoints can verify the requests came from Agentheya. At this point your hooks form an external governance layer that sees and can rewrite every step of every conversation. The detailed reference for each of these follows below.

## Payload

Every payload includes:

```json theme={null}
{
  "event": "pre_query",
  "agent_id": "abc123xyz",
  "agent_name": "my-agent",
  "user_id": "usr_owner",
  "session_id": "sess_8fk2m",
  "power_user_id": "eu_abc",
  "llm_call_id": "k3m9p2qx7n",
  "ts": "2026-05-21T11:42:03.411Z",
  "messages_context": [
    { "role": "user", "text": "What is my account balance?" },
    { "role": "assistant", "text": "Your balance is £142.50." }
  ]
}
```

`messages_context` contains the last five turns (truncated to 500 chars each) so your endpoint always has conversational context without needing to maintain its own session state.

Event-specific fields are included on top:

* `on_user_message`: `query`, `user_name`, `caller_user_id`, `caller_ip`, `accessed_via`
* `pre_query`: `query`, `system_prompt`, `sections_used`, `retrieval_hits_count`, `tool_names`, `model`
* `pre_tool_call` / `post_tool_call`: `tool`, `args`, and (on post) `result`
* `post_query`: `user_message`, `agent_response`, `tool_calls`, token usage

## Response protocol

Your endpoint returns JSON. Returning `2xx` with no body continues normally.

### Decision field

```json theme={null}
{ "decision": "continue" }
```

| Value                                  | Meaning                                                     |
| -------------------------------------- | ----------------------------------------------------------- |
| `"continue"` / `"allow"` / `"approve"` | Proceed normally (also the default on 2xx with no body).    |
| `"block"` / `"deny"`                   | Halt the operation. `reason` is logged internally as a 403. |

### User-visible blocking

When you block a request and want the user to see a friendly message instead of an error:

```json theme={null}
{
  "decision": "block",
  "reason": "quota exceeded (internal log)",
  "block_message": "You've used all your queries for today. Come back tomorrow!"
}
```

`block_message` is returned to the user as an assistant response in the chat. `reason` is logged server-side only.

### UI notifications

Surface a markdown notification to the user before the LLM responds (non-blocking path):

```json theme={null}
{
  "decision": "continue",
  "ui_message": "⚠️ Your account is in arrears. Some features may be limited."
}
```

The notification appears as a separate message before the assistant's response.

## Full prompt control (pre\_query)

`pre_query` blocking hooks have full control over what the LLM sees.

### Replace the system prompt

```json theme={null}
{
  "decision": "continue",
  "system_prompt_override": "You are a strict refund specialist. Only discuss order refunds. Refuse all other topics politely."
}
```

Replaces the entire agent system prompt you configured. Platform-level content and safety policies still apply.

### Append context

```json theme={null}
{
  "decision": "continue",
  "additional_context": "User tier: enterprise. Account region: EU. VAT registered: yes."
}
```

Appended to the system prompt under `<hook_context>`. Lighter-weight than full replacement when you only need to inject runtime data.

### Replace the messages array

```json theme={null}
{
  "decision": "continue",
  "messages_override": [
    { "role": "user", "content": "Summarise the following document: ..." },
    { "role": "assistant", "content": "Here is a summary..." },
    { "role": "user", "content": "Now translate it to French." }
  ]
}
```

Replaces the entire conversation history the LLM sees. Use for conversation rewriting, context truncation, or injecting structured history from an external store. Format: a standard `MessageParam[]` array (role + content blocks).

### Inject messages

```json theme={null}
{
  "decision": "continue",
  "messages_append": [
    { "role": "user", "content": "Context: the user is a premium subscriber in the EU region." },
    { "role": "assistant", "content": "Understood. I will apply EU consumer protections." }
  ]
}
```

Appended to the end of the conversation before the LLM call. Lets you simulate a prior exchange without replacing the full history.

## Tool interception (pre\_tool\_call)

### Rewrite arguments

```json theme={null}
{
  "decision": "continue",
  "args_override": { "query": "sanitised query", "limit": 5 }
}
```

The tool runs with your version of the arguments.

### Skip execution and return a synthetic result

```json theme={null}
{
  "decision": "continue",
  "result_override": { "cached": true, "data": { "weather": "sunny", "temp_c": 22 } }
}
```

The tool body is skipped entirely. The model receives `result_override` as the tool result. Use for caching, mocking, or replacing external calls at runtime.

### Block a tool call

```json theme={null}
{
  "decision": "block",
  "block_message": "I'm not able to access that database for you right now."
}
```

The tool fails with `block_message` as the error the LLM sees. The LLM can then relay this to the user naturally in its response.

## Post-tool result rewriting (post\_tool\_call)

```json theme={null}
{
  "decision": "continue",
  "result_transform": { "rows": ["...(redacted)..."] }
}
```

Replaces the result the model sees after the tool ran. Use to redact PII, summarise large outputs, or normalise formats.

## Post-query response replacement (post\_query, blocking)

Configure the hook as blocking. Return:

```json theme={null}
{
  "decision": "continue",
  "response_override": "I'm sorry, I cannot discuss that topic."
}
```

Replaces the assistant's final response before it reaches the user.

## Request signing (HMAC-SHA256)

Set a **Signing secret** on a hook to enable Standard Webhooks compatible request signing. Every request will carry three headers:

| Header              | Value                         |
| ------------------- | ----------------------------- |
| `webhook-id`        | Unique UUID for this delivery |
| `webhook-timestamp` | Unix timestamp (seconds)      |
| `webhook-signature` | `v1,<base64(HMAC-SHA256)>`    |

The signed content is: `"<webhook-id>.<webhook-timestamp>.<request-body>"`.

Verification example (Python):

```python theme={null}
import hmac, hashlib, base64, time

def verify_webhook(secret, headers, body):
    wh_id = headers["webhook-id"]
    wh_ts = headers["webhook-timestamp"]
    # Reject stale deliveries (>5 min clock skew)
    if abs(time.time() - int(wh_ts)) > 300:
        return False
    signed = f"{wh_id}.{wh_ts}.{body}"
    expected = base64.b64encode(
        hmac.new(secret.encode(), signed.encode(), hashlib.sha256).digest()
    ).decode()
    # webhook-signature may list multiple versions: "v1,<sig1> v1,<sig2>"
    sigs = [s.split(",", 1)[1] for s in headers["webhook-signature"].split()]
    return any(hmac.compare_digest(s, expected) for s in sigs)
```

## Advanced options

**Custom headers** — add `Authorization: Bearer <token>` or `X-Api-Key: <key>` for endpoint authentication.

**Timeout** — maximum wait time in milliseconds (default: 5000, range 500–60000). Only applies to blocking hooks. Blocking hooks that time out treat the request as blocked (fail-closed). Increase this for hooks that do heavy work.

**Method** — every hook is delivered as `POST` with a JSON body.

**Tool filter** (`pre_tool_call` / `post_tool_call` only) — restrict a hook to specific tools by name pattern (regex), by selecting from the tool list (**Select tools** button), or both.

**Alert filter** (`on_alert` only) — restrict by minimum severity (`info`, `warning`, `critical`).

**Hook order** — drag the hook card by its header to reorder. Order matters for blocking hooks in the same event (see "Multiple hooks" above).

## Multiple hooks, chain behaviour

You can have multiple blocking hooks for the same event. They run sequentially. The first `block` decision halts the chain immediately — later hooks do not run. `continue` hooks run to completion and their rewrites accumulate (last writer wins for `system_prompt_override`, `messages_override`, `args_override`; appended for `messages_append`, `additional_context`).

## Example — CRM context injection

Configure a **Before query** hook pointing at an endpoint that fetches the user's CRM record and returns it as `additional_context`. Zero latency impact if you keep the lookup under your timeout budget.

```json theme={null}
{
  "decision": "continue",
  "additional_context": "CRM record: name=Jane Smith, tier=gold, open_tickets=2, last_contact=2026-05-10"
}
```

## Example — compliance guardrail

Configure an **On user message** hook that sends the message to your moderation API. Block and show a user-friendly message if it violates policy.

```json theme={null}
{
  "decision": "block",
  "reason": "policy_violation:financial_advice",
  "block_message": "I'm not able to provide personalised financial advice. Please contact a qualified advisor."
}
```

## Manage via the Management MCP

This page can be configured programmatically through the [Management MCP](/manage-mcp) — useful for AI agents and CI pipelines.

|               |                                                                                         |
| ------------- | --------------------------------------------------------------------------------------- |
| Endpoint      | `https://manage.agentheya.com/mcp`                                                      |
| Tool          | `config.set`                                                                            |
| Section       | `hooks`                                                                                 |
| Schema (HTTP) | [`GET /api/manage-mcp/schema/hooks`](https://agentheya.com/api/manage-mcp/schema/hooks) |

Example:

```json theme={null}
{
  "tool": "config.set",
  "owner_id": "<your owner_id>",
  "agent_id": "<your agent_id>",
  "section": "hooks",
  "data": { /* see schema for accepted fields */ }
}
```

Partial updates are supported — only the fields you include are changed. Call `config.get` to read the current values and `schema.get` (or the HTTP schema URL above) to see the full field list.
