webhook/¶
HTTP ingress for external event triggers.
Files¶
models.py:WebhookEntry,WebhookResult,render_templatemanager.py: persistence CRUD for hooksauth.py: bearer/hmac validation + rate limiterserver.py: aiohttp server and request validation chainobserver.py: server lifecycle, dispatch logic, watcherinfra/task_runner.py(shared): folder checks + one-shot task execution for webhook/cron/background
Persistence¶
File: ~/.sygen/webhooks.json
- format:
{ "hooks": [...] } - atomic writes
- mtime watcher reloads hooks every 5s on changes
Hook model highlights¶
Core fields:
id,title,mode,prompt_template,enabledtask_folder(cron_taskmode)- trigger/error telemetry fields
Auth fields:
auth_mode(bearerorhmac)- per-hook token/secret and hmac config options
Bearer fallback behavior:
- for
auth_mode="bearer", validation useshook.tokenwhen set - otherwise it falls back to global
webhooks.token
Execution overrides (cron_task):
provider,model,reasoning_effort,cli_parametersquiet_start,quiet_end,dependency
Quiet-hour note:
cron_taskquiet hours are evaluated only from hook-levelquiet_start/quiet_end.- no fallback to global heartbeat quiet hours.
Template rendering (render_template):
- placeholder syntax:
{{field}} - source: top-level keys in incoming JSON payload
- missing key behavior: rendered as
{{?field}}(visible but non-fatal) - rendering never raises for missing placeholders
Server routes¶
GET /healthPOST /hooks/{hook_id}
Validation order for POST:
- rate limit
- content type
- JSON object body
- hook exists
- hook enabled
- auth validation
- dispatch async (
202response immediately)
Observer startup¶
When webhooks.enabled=true:
- auto-generate global webhook token if empty and persist to config
- start server
- start watcher loop
Dispatch flow¶
WebhookObserver._dispatch(hook_id, payload):
- lookup hook
- render template
- wrap rendered prompt with external-input safety markers
- route by mode:
wakecron_task- record trigger + error status
- invoke optional result callback
Mode: wake¶
- observer calls wake handler for each
allowed_user_id - bot wake handler acquires per-chat lock and routes through normal orchestrator message flow
- result status is
successonly if at least one non-empty response is produced
Current transport limitation:
wakeis currently wired by Telegram startup- Matrix startup does not currently provide a wake handler, so webhook
wakeon Matrix-only setups returnserror:no_wake_handler
Mode: cron_task¶
One-shot isolated run in task folder:
- validate
task_folder - quiet-hour gate (hook-level only; no heartbeat quiet-hour fallback)
- dependency lock
- resolve task execution config
- build provider command (Claude/Codex/Gemini)
- execute with timeout
- parse result and return
WebhookResult
Common result statuses¶
successerror:not_founderror:no_wake_handlererror:no_responseerror:no_task_foldererror:folder_missingerror:cli_not_found_claudeerror:cli_not_found_codexerror:cli_not_found_geminierror:timeouterror:exit_<code>skipped:quiet_hourserror:unknown_mode_<mode>error:exception
Security notes¶
- default bind:
127.0.0.1:8742 - per-hook bearer/hmac auth
- request body size limit (
max_body_bytes) - sliding-window rate limit
- external payload boundary markers injected into prompt