# Schedules and webhooks An agent normally runs when your code, the CLI or the console calls it. Triggers let it run on its own. A triggered run is one-shot: a fresh session whose VM ends when the run finishes, with no `end_user_id` (memory, if on, uses the owner's bucket). A run that pauses for an approval keeps its VM until the approval is decided. ## Schedules A schedule is a cron (5 fields, in a time zone) plus the message the agent gets each time. It uses orkestr's cron system, so it shows on the Crons page with its run history, and the plan's cron limits apply: Free 1 cron, at most daily; Pro 10, at most every 5 minutes; Enterprise 30, at most every minute. ```bash orkestr agents schedule add analyst --cron "0 9 * * 1-5" --tz Europe/Berlin \ --message "Summarise last night's errors." orkestr agents schedule list analyst orkestr agents schedule rm analyst ``` **Python:** ```python agent.add_schedule("0 9 * * 1-5", "Summarise last night's errors.", timezone="Europe/Berlin") agent.schedules() ``` **JavaScript:** ```javascript await agent.addSchedule("0 9 * * 1-5", "Summarise last night's errors.", { timezone: "Europe/Berlin", }); await agent.schedules(); ``` **cURL:** ```bash curl -X POST https://api.orkestr.eu/v1/agents/analyst/schedules \ -H "Authorization: Bearer $ORKESTR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"schedule": "0 9 * * 1-5", "timezone": "Europe/Berlin", "message": "Summarise last night'\''s errors."}' ``` ## Webhooks A webhook is a URL that runs the agent when another service POSTs to it, such as a GitHub repo, a monitoring alert or a form tool. The request body becomes the input, optionally wrapped in a template where `{{body}}` is replaced. The body is never stored. ```bash orkestr agents webhook add analyst "github issues" \ --template $'Triage this GitHub issue:\n{{body}}' --daily-cap 50 # prints the URL and the secret, once ``` **Python:** ```python hook = agent.add_webhook("github issues", input_template="Triage this GitHub issue:\n{{body}}", daily_cap=50) print(hook["url"]) # https://api.orkestr.eu/v1/hooks/agents/awh_... print(hook["secret"]) # whs_... shown once: store it now ``` **JavaScript:** ```javascript const hook = await agent.addWebhook("github issues", { inputTemplate: "Triage this GitHub issue:\n{{body}}", dailyCap: 50, }); console.log(hook.url); // https://api.orkestr.eu/v1/hooks/agents/awh_... console.log(hook.secret); // whs_... shown once: store it now ``` **cURL:** ```bash curl -X POST https://api.orkestr.eu/v1/agents/analyst/webhooks \ -H "Authorization: Bearer $ORKESTR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "github issues", "input_template": "Triage this GitHub issue:\n{{body}}"}' ``` ## Calling a webhook The sender proves it holds the secret in one of two ways: - As a Bearer token: `Authorization: Bearer whs_...` - By signing the raw body with HMAC-SHA256 and sending `sha256=` in `X-Orkestr-Signature` or `X-Hub-Signature-256`. That is GitHub's scheme, so a GitHub repo webhook works as is: paste the URL, set content type to JSON, and use the secret as the webhook's secret. GitHub's `ping` event is answered without running the agent. ```bash curl -X POST https://api.orkestr.eu/v1/hooks/agents/awh_... \ -H "Authorization: Bearer whs_..." \ -d '{"title": "Login button broken"}' # 202 {"run_id": "run_...", "session_id": "ses_...", "status": "queued"} ``` - Unknown webhook or wrong secret: `401` (the same answer for both). - Turned off: `409 webhook_disabled`. - Over its daily cap (1 to 1,000 runs a UTC day, default 50): `429 daily_cap_reached`. - Body over 100 KB: `413`. > **Rotating** > > `orkestr agents webhook rotate analyst ` makes a new secret; the old one stops working at once. The console's **Triggers** panel on the agent page does the same, and can switch a schedule or webhook off without deleting it.