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

# Webhooks

> Trigger background forks from external services via authenticated HTTP endpoints.

Webhooks let external services trigger ollim-bot background forks over
HTTP. A CI pipeline can notify the bot about a failed build, a monitoring
tool can report downtime, or any service that speaks HTTP can send
structured data for the agent to act on.

Webhook specs are markdown files with YAML frontmatter — the same format
as [routines](/scheduling/routines) and [reminders](/scheduling/reminders).
The agent can create and manage them conversationally through file access.

## Prerequisites

* ollim-bot running ([quickstart](/getting-started/quickstart))
* Two environment variables set in `.env`:

| Variable         | Required | Default | Description                              |
| ---------------- | -------- | ------- | ---------------------------------------- |
| `WEBHOOK_PORT`   | Yes      | —       | Port for the HTTP server (e.g. `8420`)   |
| `WEBHOOK_SECRET` | Yes      | —       | Secret token for authenticating requests |

<Note>
  If `WEBHOOK_PORT` is set but `WEBHOOK_SECRET` is missing, the server
  refuses to start.
</Note>

## Setup

<Steps>
  <Step title="Set environment variables">
    Add both variables to your `.env` file:

    ```bash theme={null}
    WEBHOOK_PORT=8420
    WEBHOOK_SECRET=my-webhook-secret-token
    ```
  </Step>

  <Step title="Create the webhooks directory">
    ```bash theme={null}
    mkdir -p ~/.ollim-bot/webhooks
    ```
  </Step>

  <Step title="Write a webhook spec">
    Create a markdown file in `~/.ollim-bot/webhooks/`. The filename
    is arbitrary — the `id` field in the frontmatter determines the
    URL path.

    ```yaml title="~/.ollim-bot/webhooks/github-ci.md" theme={null}
    ---
    id: github-ci
    isolated: true
    model: haiku
    allow-ping: true
    update-main-session: on_ping
    skills:
      - "repo-status"
    fields:
      type: object
      required: [repo, status]
      properties:
        repo:
          type: string
          maxLength: 200
        branch:
          type: string
          maxLength: 200
        status:
          type: string
          enum: [success, failure, cancelled]
        url:
          type: string
          format: uri
          maxLength: 500
      additionalProperties: false
    ---
    GitHub Actions CI result:
    - Repository: {repo}
    - Branch: {branch}
    - Status: {status}
    - URL: {url}

    Check the build status and decide whether this warrants
    my attention.
    ```
  </Step>

  <Step title="Restart the bot">
    The webhook server starts automatically when the bot launches.
    You'll see in the logs:

    ```text theme={null}
    Webhook server started on 127.0.0.1:8420
    ```
  </Step>

  <Step title="Send a test request">
    ```bash theme={null}
    curl -X POST http://127.0.0.1:8420/hook/github-ci \
      -H "Authorization: Bearer my-webhook-secret-token" \
      -H "Content-Type: application/json" \
      -d '{"repo": "ollim-bot", "branch": "main",
           "status": "failure",
           "url": "https://github.com/Ollim-AI/ollim-bot/actions/runs/123"}'
    ```

    A successful request returns `202 Accepted` with
    `{"status": "accepted"}`.
  </Step>
</Steps>

## Spec file format

Each webhook spec is a `.md` file in `~/.ollim-bot/webhooks/` with
YAML frontmatter and a markdown body.

### Frontmatter fields

| Field                 | Type                   | Default   | Description                                                                                 |
| --------------------- | ---------------------- | --------- | ------------------------------------------------------------------------------------------- |
| `id`                  | `string`               | —         | URL path — requests go to `/hook/{id}`                                                      |
| `fields`              | `object`               | —         | Validation rules for the payload (JSON Schema)                                              |
| `isolated`            | `bool`                 | `false`   | Run the fork in isolated mode                                                               |
| `model`               | `string`               | `null`    | Override the model (e.g. `haiku`)                                                           |
| `thinking`            | `bool`                 | `true`    | Enable extended thinking                                                                    |
| `allow-ping`          | `bool`                 | `true`    | Allow the fork to ping the user                                                             |
| `update-main-session` | `string`               | `on_ping` | When to write back to main session                                                          |
| `skills`              | `list[string] \| null` | `null`    | [Skill](/extending/skills) names to load at fire time                                       |
| `subagent`            | `string \| null`       | `null`    | [Subagent](/extending/subagents) to delegate the core task to                               |
| `reflect`             | `bool`                 | `true`    | Write a [reflection trace](/scheduling/background-forks#reflections) when the fork finishes |

<Tip>
  The `isolated`, `model`, `thinking`, `allow-ping`, and
  `update-main-session` fields have the same semantics as
  [background fork configuration](/scheduling/background-forks).
</Tip>

### Markdown body

The body is a prompt template — instructions for what the agent should
do with the incoming data. The validated JSON payload is passed to the
agent separately (as a fenced data block in the prompt), so you don't
need placeholders. Just describe the task and reference field names
naturally.

### Fields schema

The `fields` value is a set of validation rules using JSON Schema.
It defines which fields the webhook accepts, their types, and any
constraints.

<Accordion title="Schema authoring tips">
  * Use `enum` over free `string` wherever values are known
  * Use `integer` / `boolean` over `string` for non-text data
  * Always set `maxLength` on string fields (default 500 if omitted)
  * Always set `additionalProperties: false`
</Accordion>

## How requests are processed

When an HTTP request hits `/hook/{id}`, the bot authenticates it,
validates the payload against the spec's field rules, and fires a
[background fork](/scheduling/background-forks) with the validated
data. The caller receives `202 Accepted` immediately — the bot
processes the webhook asynchronously.

Invalid requests are rejected with descriptive errors:

| Condition                   | Status | Response body                                      |
| --------------------------- | ------ | -------------------------------------------------- |
| Bad or missing Bearer token | `401`  | `{"error": "unauthorized"}`                        |
| Unknown webhook path        | `404`  | `{"error": "webhook not found: <slug>"}`           |
| Invalid JSON body           | `400`  | `{"error": "invalid json"}`                        |
| Schema validation failure   | `400`  | `{"error": "validation failed", "details": [...]}` |

<AccordionGroup>
  <Accordion title="Request processing pipeline">
    1. **Auth check** — verifies the secret token in the request header.
    2. **Spec lookup** — matches the URL path to a spec's `id` field.
    3. **JSON parse** — reads the request body as JSON.
    4. **Schema validation** — validates the payload against the spec's `fields` rules.
    5. **Prompt construction** — the bot builds a prompt combining the task instructions (markdown body) with the validated JSON payload as a fenced data block, keeping webhook data clearly separated from instructions.
    6. **202 Accepted** — response sent immediately.
    7. **Subagent validation** — if the spec references a `subagent`, the bot verifies it exists. Unknown subagents skip dispatch entirely.
    8. **Injection screening** — free-form text fields are checked for attempts to trick the bot (30-second timeout — if screening fails, the webhook proceeds). Flagged payloads are skipped and logged.
    9. **Skill validation** — if the spec references `skills`, the bot verifies each one exists in `~/.ollim-bot/skills/`. Missing skills skip dispatch entirely.
    10. **Dispatch** — a background fork runs with the spec's
        configuration.
  </Accordion>

  <Accordion title="Input security layers">
    Webhooks accept external input, so four layers of defense prevent
    outside data from tricking the bot:

    | Layer                   | What it catches                                             |
    | ----------------------- | ----------------------------------------------------------- |
    | **Schema validation**   | Wrong types, undeclared fields, oversized strings           |
    | **Data labeling**       | Webhook data is clearly separated from bot instructions     |
    | **Injection screening** | Free-form text checked for attempts to trick the bot        |
    | **Size limits**         | 10 KB payload cap, 500-character strings, 20 properties max |

    <Warning>
      If the injection screening check itself fails (e.g. a network
      error), the webhook is processed normally rather than blocked.
      This prevents screening outages from blocking legitimate webhooks.
    </Warning>
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Webhook server not starting">
    Check that both `WEBHOOK_PORT` and `WEBHOOK_SECRET` are set in
    your `.env` file. If only `WEBHOOK_PORT` is set, the server logs
    an error and stays disabled.
  </Accordion>

  <Accordion title="Getting 401 Unauthorized">
    Verify your `Authorization` header matches
    `Bearer <WEBHOOK_SECRET>` exactly.
  </Accordion>

  <Accordion title="Getting 404 for a webhook path">
    Specs are re-read each time a request arrives, so changes take
    effect immediately without restarting. Verify the file exists in
    `~/.ollim-bot/webhooks/` and that the `id` field in the frontmatter
    matches the path in your URL.
  </Accordion>

  <Accordion title="Webhook accepted but no bot action">
    Several things can cause a webhook to be accepted (202) but produce no action:

    * **Injection screening** flagged a field value. Look for `Webhook <slug>: flagged fields [...], skipping dispatch` in the logs.
    * **Missing skills** — the spec references a skill that doesn't exist in `~/.ollim-bot/skills/`. Look for `Webhook <slug>: unknown skills [...], skipping`.
    * **Missing subagent** — the spec references a subagent that doesn't exist. Look for `Webhook <slug>: unknown subagent '...', skipping`.
  </Accordion>
</AccordionGroup>

## Next steps

<Columns cols={2}>
  <Card title="Background forks" icon="code-branch" href="/scheduling/background-forks">
    How background forks work — the execution model webhooks use.
  </Card>

  <Card title="Ping budget" icon="bell" href="/scheduling/ping-budget">
    How the ping budget controls when forks can notify you.
  </Card>

  <Card title="Google integration" icon="google" href="/integrations/google-overview">
    Connect Google services for tasks, calendar, and email access.
  </Card>

  <Card title="Discord tools" icon="wrench" href="/extending/mcp-tools">
    The tools available to the agent during webhook-triggered forks.
  </Card>
</Columns>
