openclaw/docs/automation/webhook.md
Bradley Priest 6b82fabdbe docs(hooks): document transform functions
Add Transform Functions section to webhook docs covering:
- Basic config and JS example
- Context object type (HookMappingContext)
- Return values for agent/wake actions
- Returning null to skip webhooks
- Async transforms
- TypeScript loader requirements
- JS works out of the box
2026-01-30 11:54:52 +13:00

266 lines
8.5 KiB
Markdown

---
summary: "Webhook ingress for wake and isolated agent runs"
read_when:
- Adding or changing webhook endpoints
- Wiring external systems into Moltbot
---
# Webhooks
Gateway can expose a small HTTP webhook endpoint for external triggers.
## Enable
```json5
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks"
}
}
```
Notes:
- `hooks.token` is required when `hooks.enabled=true`.
- `hooks.path` defaults to `/hooks`.
## Auth
Every request must include the hook token. Prefer headers:
- `Authorization: Bearer <token>` (recommended)
- `x-moltbot-token: <token>`
- `?token=<token>` (deprecated; logs a warning and will be removed in a future major release)
## Endpoints
### `POST /hooks/wake`
Payload:
```json
{ "text": "System line", "mode": "now" }
```
- `text` **required** (string): The description of the event (e.g., "New email received").
- `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
Effect:
- Enqueues a system event for the **main** session
- If `mode=now`, triggers an immediate heartbeat
### `POST /hooks/agent`
Payload:
```json
{
"message": "Run this",
"name": "Email",
"sessionKey": "hook:email:msg-123",
"wakeMode": "now",
"deliver": true,
"channel": "last",
"to": "+15551234567",
"model": "openai/gpt-5.2-mini",
"thinking": "low",
"timeoutSeconds": 120
}
```
- `message` **required** (string): The prompt or message for the agent to process.
- `name` optional (string): Human-readable name for the hook (e.g., "GitHub"), used as a prefix in session summaries.
- `sessionKey` optional (string): The key used to identify the agent's session. Defaults to a random `hook:<uuid>`. Using a consistent key allows for a multi-turn conversation within the hook context.
- `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
- `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging channel. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped.
- `channel` optional (string): The messaging channel for delivery. One of: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `mattermost` (plugin), `signal`, `imessage`, `msteams`. Defaults to `last`.
- `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack/Mattermost (plugin), conversation ID for MS Teams). Defaults to the last recipient in the main session.
- `model` optional (string): Model override (e.g., `anthropic/claude-3-5-sonnet` or an alias). Must be in the allowed model list if restricted.
- `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
Effect:
- Runs an **isolated** agent turn (own session key)
- Always posts a summary into the **main** session
- If `wakeMode=now`, triggers an immediate heartbeat
### `POST /hooks/<name>` (mapped)
Custom hook names are resolved via `hooks.mappings` (see configuration). A mapping can
turn arbitrary payloads into `wake` or `agent` actions, with optional templates or
code transforms.
Mapping options (summary):
- `hooks.presets: ["gmail"]` enables the built-in Gmail mapping.
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
- Use `match.source` to keep a generic ingest endpoint (payload-driven routing).
- TS transforms require a TS loader (e.g. `bun` or `tsx`) or precompiled `.js` at runtime.
- Set `deliver: true` + `channel`/`to` on mappings to route replies to a chat surface
(`channel` defaults to `last` and falls back to WhatsApp).
- `allowUnsafeExternalContent: true` disables the external content safety wrapper for that hook
(dangerous; only for trusted internal sources).
- `moltbot webhooks gmail setup` writes `hooks.gmail` config for `moltbot webhooks gmail run`.
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
## Transform Functions
When templates aren't flexible enough, use a transform function to process webhook
payloads with code.
### Basic Example
```yaml
hooks:
enabled: true
token: "secret"
transformsDir: ./hooks # relative to config file
mappings:
- id: github
match:
path: github
action: agent
transform:
module: github.js # resolves to ./hooks/github.js
export: handleWebhook # optional, defaults to "default" or "transform"
```
The transform function receives a context object:
```typescript
type HookMappingContext = {
payload: Record<string, unknown>; // Parsed JSON body
headers: Record<string, string>; // Lowercase header names
url: URL; // Parsed request URL
path: string; // Subpath after /hooks/ (e.g., "github")
};
```
```javascript
// hooks/github.js
export function handleWebhook(ctx) {
const event = ctx.headers["x-github-event"];
// Return null to skip this webhook entirely (no agent run)
if (event === "ping") return null;
// Return fields to override the mapping defaults
return {
message: `GitHub ${event}: ${ctx.payload.action} on ${ctx.payload.repository?.full_name}`,
name: "GitHub",
sessionKey: `github:${ctx.payload.repository?.id}`,
};
}
```
### Return Values
Return an object with fields to override, or `null` to skip:
```typescript
// For action: "agent" (default)
return {
message: string; // Required if not set in mapping
name?: string; // Display name for the hook
sessionKey?: string; // Session identifier
wakeMode?: "now" | "next-heartbeat";
deliver?: boolean; // Send response to chat
channel?: string; // Target channel
to?: string; // Recipient
model?: string; // Model override
thinking?: string; // Thinking level
timeoutSeconds?: number;
};
// For action: "wake"
return {
text: string; // Required
mode?: "now" | "next-heartbeat";
};
// Skip this webhook (no action taken, returns 204)
return null;
```
### Async Transforms
Transforms can be async:
```javascript
export default async function(ctx) {
const extra = await fetchAdditionalContext(ctx.payload.id);
return {
message: `Event: ${ctx.payload.type}\nContext: ${extra}`,
};
}
```
### TypeScript
JavaScript (`.js` / `.mjs`) transforms work out of the box with no extra setup.
TypeScript transforms require a loader at runtime:
```bash
# Run gateway with tsx
npx tsx node_modules/.bin/moltbot gateway start
# Or use bun
bun run moltbot gateway start
# Or precompile to .js
tsc hooks/*.ts --outDir hooks-dist
# then reference hooks-dist/github.js in config
```
## Responses
- `200` for `/hooks/wake`
- `202` for `/hooks/agent` (async run started)
- `401` on auth failure
- `400` on invalid payload
- `413` on oversized payloads
## Examples
```bash
curl -X POST http://127.0.0.1:18789/hooks/wake \
-H 'Authorization: Bearer SECRET' \
-H 'Content-Type: application/json' \
-d '{"text":"New email received","mode":"now"}'
```
```bash
curl -X POST http://127.0.0.1:18789/hooks/agent \
-H 'x-moltbot-token: SECRET' \
-H 'Content-Type: application/json' \
-d '{"message":"Summarize inbox","name":"Email","wakeMode":"next-heartbeat"}'
```
### Use a different model
Add `model` to the agent payload (or mapping) to override the model for that run:
```bash
curl -X POST http://127.0.0.1:18789/hooks/agent \
-H 'x-moltbot-token: SECRET' \
-H 'Content-Type: application/json' \
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.2-mini"}'
```
If you enforce `agents.defaults.models`, make sure the override model is included there.
```bash
curl -X POST http://127.0.0.1:18789/hooks/gmail \
-H 'Authorization: Bearer SECRET' \
-H 'Content-Type: application/json' \
-d '{"source":"gmail","messages":[{"from":"Ada","subject":"Hello","snippet":"Hi"}]}'
```
## Security
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
- Use a dedicated hook token; do not reuse gateway auth tokens.
- Avoid including sensitive raw payloads in webhook logs.
- Hook payloads are treated as untrusted and wrapped with safety boundaries by default.
If you must disable this for a specific hook, set `allowUnsafeExternalContent: true`
in that hook's mapping (dangerous).