Skip to content

Webhook Automation with Sync + Async Endpoints

This playbook wires the agent into an automation pipeline over HTTP: a fast sync endpoint that returns the answer in the response body, and a long-running async endpoint that returns 202 plus a status URL you poll until the job completes. It also shows how to run several named endpoints on one shared port, defined inline or as webhook/*.md files.

Backend developers integrating the agent into a pipeline — calling it from scripts, CI jobs, or other services rather than a chat channel.

Accept fast sync HTTP calls and long-running async jobs (202 + status polling) across multiple named endpoints, some defined as markdown files.

The first three are config coverage (the webhook section plus MONO_AGENT_WEBHOOK_* env overrides); native notification is opt-in per endpoint via notify: true.

mono-agent init already enables the webhook channel with a single sync endpoint. The config below adds a second async endpoint and a per-endpoint prompt. Each endpoint needs a unique name and a unique path; a duplicate of either (across inline config and folder files) is a hard configuration error.

{
"runtime": {
"model": "anthropic:claude-sonnet-4-6"
},
"webhook": {
"enabled": true,
"host": "127.0.0.1",
"port": 8080,
"defaultMode": "sync",
"endpoints": [
{
"name": "invoke",
"path": "/webhook/invoke",
"mode": "sync",
"prompt": "Respond to this request:"
},
{
"name": "jobs",
"path": "/webhook/jobs",
"mode": "async"
}
],
"retentionMs": 300000,
"maxStoredRequests": 100
}
}

The matching env overrides are MONO_AGENT_WEBHOOK_HOST, MONO_AGENT_WEBHOOK_PORT, MONO_AGENT_WEBHOOK_DEFAULT_MODE, MONO_AGENT_WEBHOOK_API_KEY, MONO_AGENT_WEBHOOK_RETENTION_MS, MONO_AGENT_WEBHOOK_MAX_STORED_REQUESTS, MONO_AGENT_WEBHOOK_NOTIFY, MONO_AGENT_WEBHOOK_NOTIFY_CONVERSATION_ID, and MONO_AGENT_WEBHOOK_ENDPOINTS_JSON (the endpoints array as a JSON string). Set MONO_AGENT_WEBHOOK_API_KEY to require Authorization: Bearer <key> on both invocation and status polling; it is optional for this loopback recipe.

Alongside (or instead of) webhook.endpoints[], author one *.md file per endpoint in webhook.dir (default webhook/). YAML frontmatter holds routing metadata; the markdown body becomes the endpoint’s prompt, which is prepended to the incoming request text before the turn runs.

---
path: /webhook/triage
name: triage
mode: async
---
You are triaging an inbound support ticket. Classify and summarize.

path is required; name defaults to the filename stem, mode to defaultMode, enabled to true, and notify to false. This mirrors how cron jobs can be authored as cron/*.md files.

An operator-owned automation can push an async webhook run’s own final answer into a configured chat — no agent-facing tool involved. Set notify: true on the endpoint; its final answer is then delivered verbatim to a destination resolved in this order:

  1. the endpoint’s configured notifyConversationId, if set; otherwise
  2. the inbound request’s own conversationId, when a trusted caller deliberately supplies a deliverable chat (telegram:… / slack:…); otherwise
  3. the single notify-capable destination, when exactly one exists.

For example, an authenticated automation that already owns its destination can call the endpoint with a fixed conversationId; when the endpoint finishes, its answer is recorded in that conversation’s history. The destination remains bounded by the owning channel’s allowlist, and a payload-supplied id outside telegram.allowedChatIds / slack.allowedChannelIds (or allowAll*) is refused. If there is nothing worth sending, the agent replies with exactly NOTHING_TO_REPORT and no notification is delivered.

This generic webhook feature does not prove that a later result belongs to the chat that initiated external work. For that workflow, select the external MCP service under tools.continuationServers and use a durable continuation. The host then retains the origin/thread and gives the service an opaque claim capability; the model does not copy a conversation ID or choose the callback destination. See Native proactive notification.

  1. mono-agent init --model anthropic:claude-sonnet-4-6 — the webhook channel is enabled by init already.
  2. Add multiple endpoints in webhook.endpoints[] and/or webhook/*.md files, giving each a unique name AND a unique path.
  3. Run mono-agent validate, then mono-agent start.
  4. curl the sync endpoint for an immediate response body; curl the async endpoint for a 202 plus a status URL.
  5. Poll the async status URL until the job reports complete.
  6. Confirm async retention behavior — the status entry vanishes after retentionMs (300000 ms / 5 minutes above).
  7. Inspect sync, async status, and any result callback metadata and confirm metadata.summary.systemPrompt is absent even when using a custom responder.