Prerequisites
- ollim-bot running (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 | — | Bearer token for authenticating requests |
If
WEBHOOK_PORT is set but WEBHOOK_SECRET is missing, the server
refuses to start.Setup
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 slug.~/.ollim-bot/webhooks/github-ci.md
Restart the bot
The webhook server starts during
on_ready, after the scheduler.
You’ll see in the logs: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 slug — requests go to /hook/{id} |
fields | object | — | JSON Schema (Draft 7) for the payload |
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 |
Markdown body
The body is a prompt template. Use{field_name} placeholders that
correspond to properties in the fields schema. These are filled from
the validated payload data using Python’s str.format_map().
Fields schema
Thefields value is a JSON Schema object validated with the
jsonschema library (Draft 7). Best practices for spec authoring:
- Use
enumover freestringwherever values are known - Use
integer/booleanoverstringfor non-text data - Always set
maxLengthon string fields (default 500 if omitted) - Always set
additionalProperties: false
Request flow
When an HTTP request hits/hook/{slug}:
- Auth check — Bearer token compared against
WEBHOOK_SECRET(constant-time). Returns401if invalid. - Spec lookup — Iterates all specs in
~/.ollim-bot/webhooks/and matches by theidfield (filename is arbitrary). Returns404if not found. - JSON parse — Reads request body as JSON.
Returns
400if malformed. - Schema validation — Validates payload against the
fieldsJSON Schema. Returns400with error details if invalid. - 202 Accepted — Response sent immediately. The caller is done.
- Haiku screening — Free-form string fields are batched into a single Haiku call that checks for prompt injection patterns. If any field is flagged, the dispatch is skipped silently (logged).
- Prompt construction — Payload data fills the template. The
prompt is tagged
[webhook:{slug}]and includes the background preamble. - Dispatch —
run_agent_background()fires a background fork with the spec’s configuration.
Prompt structure
The constructed prompt separates untrusted data from instructions:Input security
Webhooks accept external input, so four layers of defense prevent prompt injection:| Layer | What it catches | Where |
|---|---|---|
| JSON Schema | Wrong types, undeclared fields, oversized strings | HTTP boundary |
| Content fencing | Naive injection — data labeled as untrusted | Prompt construction |
| Haiku screening | Sophisticated injection in free-form strings | Before dispatch |
| Operational limits | 10KB payload, 500-char strings, 20 properties | HTTP boundary |
Error responses
| Condition | Status | Body |
|---|---|---|
| Bad or missing Bearer token | 401 | {"error": "unauthorized"} |
| Unknown webhook slug | 404 | {"error": "webhook not found: <slug>"} |
| Invalid JSON body | 400 | {"error": "invalid json"} |
| Schema validation failure | 400 | {"error": "validation failed", "details": [...]} |
| Haiku flags injection | — | Logged, fork skipped (caller already got 202) |
Troubleshooting
Webhook server not starting
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.Getting 401 Unauthorized
Getting 401 Unauthorized
Getting 404 for a webhook slug
Getting 404 for a webhook slug
Specs are loaded from disk on every request (no caching). Verify
the file exists in
~/.ollim-bot/webhooks/ and that the id
field in the YAML frontmatter matches the slug in your URL.Webhook accepted but no bot action
Webhook accepted but no bot action
The Haiku screening layer may have flagged a field value as a
prompt injection attempt. Check the bot logs for lines like
Webhook <slug>: flagged fields [...], skipping dispatch.