Use Case: Custom Hooks -- Build Any Integration in Any Language
Hooks are HTTP listeners backed by scripts you write in any language. You have full control: parse any payload, filter events, transform data, and inj
The Problem
Every team has unique infrastructure -- internal APIs, custom CI systems, proprietary monitoring, Slack bots, Jira workflows. Pre-built integrations never fit. You need to wire arbitrary external events into an AI agent that understands your specific context.
Solution
A hook is: HTTP endpoint + your script + AI session. That's it. The script is the glue -- write it in Bash, Python, Ruby, Go, Node, Rust, whatever runs on your machine.
How Hooks Work
External System (any HTTP POST)
|
v
Hook HTTP Listener (bind address:port)
|
| passes raw HTTP body to stdin
| passes headers as HOOK_HEADER_* env vars
| passes method, path, query as env vars
v
Your Script (any language, any logic)
|
| exit 0 + stdout → inject message into AI session
| exit non-zero → ignore (stderr logged)
v
AI Agent Session (processes message with full tool access)
You control everything between the HTTP request and what the AI sees.
Configuration
[[hooks]]
name = "my-hook"
bind = "0.0.0.0:9876"
script = "/opt/hooks/my-hook.py"
timeout = 30 # seconds (1-3600)
Activate when starting the agent:
octomind run --name my-agent --daemon --format jsonl --hook my-hook
Examples in Different Languages
Python: Jira Issue Tracker
#!/usr/bin/env python3
"""Process Jira webhook events and create actionable AI tasks."""
import json, sys, os
payload = json.load(sys.stdin)
event = os.environ.get("HOOK_HEADER_X_ATLASSIAN_WEBHOOK_EVENT", "")
if event == "jira:issue_created":
issue = payload["issue"]
key = issue["key"]
summary = issue["fields"]["summary"]
description = issue["fields"].get("description", "No description")
priority = issue["fields"]["priority"]["name"]
assignee = issue["fields"].get("assignee", {}).get("displayName", "Unassigned")
print(f"""New Jira issue {key} ({priority}): {summary}
Assigned to: {assignee}
Description:
{description}
Please:
1. Analyze if this issue relates to any recent code changes
2. Identify the relevant source files
3. Suggest an implementation approach if it's a feature, or root cause if it's a bug""")
elif event == "jira:issue_updated":
changelog = payload.get("changelog", {}).get("items", [])
status_change = next((c for c in changelog if c["field"] == "status"), None)
if status_change and status_change["toString"] == "In Review":
key = payload["issue"]["key"]
print(f"Issue {key} moved to In Review. Please review the associated code changes.")
else:
sys.exit(1) # Ignore other updates
else:
sys.exit(1) # Ignore unknown events
Node.js: Slack Bot
#!/usr/bin/env node
const payload = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8'));
// Slack sends URL verification challenges
if (payload.type === 'url_verification') {
// Can't respond directly (hook is one-way), handle elsewhere
process.exit(1);
}
// Only react to app mentions
if (payload.event?.type !== 'app_mention') {
process.exit(1);
}
const user = payload.event.user;
const text = payload.event.text.replace(/<@[A-Z0-9]+>/g, '').trim();
const channel = payload.event.channel;
console.log(`Slack request from <@${user}> in #${channel}:
${text}
Respond concisely. Format for Slack (no markdown headers, use *bold* and \`code\`).`);
Bash: Simple Git Post-Receive
#!/bin/bash
# Minimal hook: extract essentials, let the AI figure out the rest
payload=$(cat)
branch=$(echo "$payload" | jq -r '.ref' | sed 's|refs/heads/||')
# Only care about main and develop
case "$branch" in
main|develop) ;;
*) exit 1 ;;
esac
commits=$(echo "$payload" | jq -r '.commits[] | "- \(.message) (\(.author.name))"')
files=$(echo "$payload" | jq -r '.commits[].modified[]' | sort -u)
echo "Push to $branch:
$commits
Files changed:
$files
Review these changes for issues."
Ruby: Custom Monitoring Alert
#!/usr/bin/env ruby
require 'json'
payload = JSON.parse($stdin.read)
severity = payload['severity']
service = payload['service']
message = payload['message']
metrics = payload['metrics'] || {}
# Only alert on warning and critical
exit 1 unless %w[warning critical].include?(severity)
puts <<~MSG
#{severity.upcase} alert from #{service}: #{message}
Metrics: #{metrics.map { |k, v| "#{k}=#{v}" }.join(', ')}
Please:
1. Check the #{service} source code for potential causes
2. Look at recent changes that might have caused this
3. Suggest immediate mitigation steps
MSG
Go: High-Performance Webhook Processor
#!/usr/bin/env -S go run
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
type DeployEvent struct {
Environment string `json:"environment"`
Version string `json:"version"`
Status string `json:"status"`
Services []struct {
Name string `json:"name"`
Health string `json:"health"`
} `json:"services"`
}
func main() {
data, _ := io.ReadAll(os.Stdin)
var event DeployEvent
if err := json.Unmarshal(data, &event); err != nil {
os.Exit(1)
}
if event.Status != "completed" {
os.Exit(1)
}
unhealthy := []string{}
for _, s := range event.Services {
if s.Health != "healthy" {
unhealthy = append(unhealthy, s.Name)
}
}
if len(unhealthy) > 0 {
fmt.Printf("Deploy %s to %s completed but %d services unhealthy: %v\n",
event.Version, event.Environment, len(unhealthy), unhealthy)
fmt.Println("\nInvestigate the unhealthy services and suggest fixes.")
} else {
fmt.Printf("Deploy %s to %s successful. All %d services healthy.\n",
event.Version, event.Environment, len(event.Services))
fmt.Println("\nRun a quick smoke test on the key API endpoints.")
}
}
Environment Variables Available
Every hook script gets rich context about the incoming request:
| Variable | Example | Description |
|---|---|---|
HOOK_NAME |
jira-webhook |
Which hook triggered |
HOOK_METHOD |
POST |
HTTP method |
HOOK_PATH |
/webhook/jira |
Request path |
HOOK_QUERY |
token=abc |
Query string |
HOOK_CONTENT_TYPE |
application/json |
Content-Type header |
HOOK_SESSION |
my-agent |
Session name |
HOOK_HEADER_X_GITHUB_EVENT |
push |
Any header as HOOK_HEADER_* |
Use these to route different event types in a single script, validate signatures, or filter by source.
Multi-Hook Agent Architecture
Run a single agent that reacts to multiple event sources:
[[hooks]]
name = "github"
bind = "0.0.0.0:9001"
script = "/opt/hooks/github.py"
timeout = 30
[[hooks]]
name = "jira"
bind = "0.0.0.0:9002"
script = "/opt/hooks/jira.py"
timeout = 30
[[hooks]]
name = "monitoring"
bind = "0.0.0.0:9003"
script = "/opt/hooks/alerts.rb"
timeout = 15
[[hooks]]
name = "slack"
bind = "0.0.0.0:9004"
script = "/opt/hooks/slack.js"
timeout = 10
octomind run --name ops-agent --daemon --format jsonl \
--hook github \
--hook jira \
--hook monitoring \
--hook slack
One AI agent, four event sources, each with its own script in its own language. The AI maintains context across all events -- it knows about the GitHub push when the monitoring alert fires 5 minutes later.
Script Design Patterns
Filter Early
# Exit non-zero to ignore events cheaply
[ "$HOOK_HEADER_X_GITHUB_EVENT" = "push" ] || exit 1
Validate Signatures
import hmac, hashlib, os, sys
secret = os.environ.get("GITHUB_WEBHOOK_SECRET", "")
signature = os.environ.get("HOOK_HEADER_X_HUB_SIGNATURE_256", "")
body = sys.stdin.buffer.read()
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
sys.exit(1)
Craft Targeted Prompts
The message you print to stdout IS the user message the AI processes. Be specific:
# Bad: dumps raw JSON
cat # AI wastes tokens parsing irrelevant fields
# Good: extract what matters, tell AI what to do
echo "PR #${pr_number} ready for review: ${title}
Changed files: ${files}
Please review for security issues and respond with approve/reject."
Timeout for Heavy Processing
[[hooks]]
name = "heavy-processor"
bind = "0.0.0.0:9876"
script = "/opt/hooks/process.py"
timeout = 120 # 2 minutes for complex payload processing
Max timeout is 3600 seconds (1 hour).
Key Points
- Scripts can be written in any language -- Bash, Python, Node, Ruby, Go, Rust, anything executable
- You have full control: parse payloads, filter events, validate signatures, transform data
- stdout becomes the AI's input; exit code controls whether to inject or ignore
- Rich environment: HTTP method, path, headers, session name all available as env vars
- Multiple hooks on different ports feed into one agent session
- The AI maintains cross-event context -- it connects the dots between GitHub pushes, Jira tickets, and monitoring alerts
- Combine with daemon mode for persistent, always-on agents