Automation Delivery: Announce, Webhook, and None Modes
Explains where completed automation runs deliver output, how announce, webhook, and none modes behave, and how failures and reply language are handled. Useful for anyone configuring automation delivery.
Read this when
- Routing automation output to a channel or a webhook
- Tuning failure notifications, thresholds, and cooldowns
- Fixing an automation that replies in the wrong language
This section covers where a completed run delivers its output, how failures during a run or delivery are handled, and how to fix the language of replies. It is one part of the Automations guide.
Delivery and output
| Mode | What happens |
|---|---|
announce | The result is sent, then confirmed delivery is recorded at the destination |
webhook | The finished event payload is POSTed to a URL |
none | No conversation result and no notification are produced automatically |
When a primary webhook run succeeds and has no nonblank summary, the POST is deliberately skipped and deliverySuppressionReason: "empty" is recorded, which follows the same optional-output contract as announce delivery. Errors during execution still trigger the error event, summary or not.
Delivery for primary webhooks is recorded once an HTTP 2xx acknowledgment arrives. An HTTP rejection is recorded as Not delivered. Should the request possibly have reached the receiver while its response is lost or times out, delivery remains Unknown, and the transport will not retry that ambiguous send. Completion also stays unknown when delivery is required, whereas best-effort delivery may complete successfully without claiming delivery.
If gateway.publicOrigin is set and the Control UI is turned on, chat notifications carry an Inspect link into the Control UI. Announcements for command and script completion open the automation run, while isolated agent announcements open the run's session.
With announce, ownership of the result passes to the chat that receives it. The result joins the conversation once the channel confirms delivery, and the content used is what was actually sent after channel transforms and hooks. OpenClaw writes a single assistant message into that chat's transcript so the next reply can draw on it as context. The result is tagged with its job and run; retries will not duplicate it, and it may commit while a turn is running. Transcript writes stay ordered, and the running turn retains ownership of its reply. Only after confirmed delivery are destinations created and remembered routes updated; any newly created destination inherits the required sandbox policy of the source session.
A freshly committed visible result resets the destination's idle freshness, so a reply arriving inside the idle window still keeps the scheduled question. Replays of the same result, silent runs, and failed or uncertain deliveries leave freshness untouched. Recurring visible results can keep the destination idle-fresh, though the daily reset still relies on the session start time.
- When delivery goes to the creating chat, the result is written there.
- When delivery goes to a different chat or topic, the result is written only in the destination, never in the creating chat.
- If there is no external route, as with a job created in WebChat, the creating conversation gets the result.
- Jobs made through the CLI or by older releases also receive a destination transcript when they deliver to a chat, even when no creating conversation exists.
noneandwebhookwrite no conversation result.
If the destination chat belongs to another agent, the result is sent but is not added to the conversation of either agent. Channel-native payloads that lack a text projection are sent with no conversation entry and produce a warning.
Execution context is handled separately: isolated begins with no conversation history, current reads bounded creating-conversation context inside a detached run, and session:<key> relies on a persistent execution session. Neither the private run transcript nor the tool history is copied to the destination.
Where a persistent execution session is already the destination conversation, its final assistant message serves as the result, and OpenClaw appends no second copy.
Editing explicit delivery coordinates redirects future results to that chat or topic. The captured creating-conversation binding cannot be changed; altering the public sessionKey will not redirect implicit delivery. For an explicit external destination, delivery continues even if a new session is started or the destination chat is reset: the result lands in that chat's current session. Implicit creator-bound delivery needs the captured creating conversation to exist and to match its generation before a remembered external route can be resolved. Implicit agent-turn announcements keep that generation through sending and recovery, and routeless WebChat results also need it at commit. If the creating conversation was deleted or reset, either recreate the job or configure an explicit external destination. Command and script announcements keep an already resolved route with no source-generation check, which matches their existing behavior. Cancellation does not interrupt the destination's active turn. A verified matching message tool send suppresses the automatic resend, but not the conversation result.
When no external route exists, the conversation commit is what completes delivery. WebChat gets the committed result right away and returns the same message from chat.history after reconnect. Failed or uncertain external sends add no conversation result. Uncertain sends produce a warning and are never blindly replayed. If the channel confirms delivery but the conversation write fails, delivery still counts as successful and the run records a warning saying the result was not added to the conversation.
Silent-reply handling is shared by conversation results and notifications. Suppressing a caption keeps attached media intact, and internal control tokens never enter history. none turns off automatic results and notifications; primary webhook delivery stays an external-only mode.
Never set delivery.channel: "webchat": internal conversation coordinates are not channel delivery targets. To deliver to the creating WebChat conversation, leave the external route unset.
Warning
The strict SSRF guard applies to every outbound automation webhook. By default, loopback, private/internal, link-local, and other special-use targets are refused for primary delivery, completion and failure destinations, and failure-alert webhooks.
Exempt only the receiver you trust, using an exact hostname or IP:
{ cron: { webhookSsrfPolicy: { allowedHostnames: ["127.0.0.1"], }, }, }Use
dangerouslyAllowPrivateNetwork: trueunderwebhookSsrfPolicyonly when every configured automation webhook is allowed to reach trusted private-network services. Leaving the policy unset preserves strict behavior.
For channel delivery, use --announce --channel telegram --to "-1001234567890". Telegram forum topics take -1001234567890:topic:123, and OpenClaw also accepts the Telegram-owned -1001234567890:123 shorthand. Direct RPC/config callers may pass delivery.threadId as either a string or a number. Slack/Discord/Mattermost targets rely on explicit prefixes (channel:<id>, user:<id>). Matrix room IDs are case-sensitive, so use the exact room ID or the room:!room:server form from Matrix.
For announce delivery in the Control UI Automations editor, pick a channel and an explicit Account ID under Advanced to see configured conversation targets in the To field. Choosing a target keeps your selected account and infers no topic. Saving after a change to the agent, delivery mode, channel, account, or recipient clears the previous topic. These configured suggestions cover only the primary announce destination; failure-alert routing stays separate. Entering a target that is not among the suggestions is still allowed.
On hosts with several configured channels, isolated announce jobs created via automations add|create or modified via automations edit must set --channel <channel-plugin-id>, unless a provider-prefixed --to or a preserved session route picks the channel. Use --best-effort-deliver only if unresolved fallback delivery is acceptable; it selects no channel, and a delivery failure will not fail the job.
Channel announcements retry transient failures only when no payload could have reached the recipient. A successful retry records delivery without keeping the earlier attempt's error, including under best-effort delivery. The announcement retry loop never replays partial or ambiguous sends.
When announce delivery uses channel: "last" or leaves channel out, a provider-prefixed target such as telegram:123 can pick the channel before the scheduler falls back to session history or to a single configured channel. Only prefixes advertised by the loaded plugin count as provider selectors. If delivery.channel is explicit, the target prefix must name that same provider; channel: "whatsapp" with to: "telegram:123" is rejected rather than letting WhatsApp read the Telegram ID as a phone number. Target-kind and service prefixes (channel:<id>, user:<id>, imessage:<handle>, sms:<number>) remain channel-owned target syntax and are not provider selectors.
For isolated jobs, chat delivery is shared: when a chat route exists, the agent may call the message tool even with --no-deliver in place. If the agent targets the configured or current destination, OpenClaw does not run the fallback announce. In all other cases, announce, webhook, and none govern only how the runner handles the final reply once the agent turn has finished.
A send to the delivery target does not close out the job's turn unless it sets final: true, so any steps that follow the send, such as writing a state file, still execute.
Scheduled message actions run through the Gateway that owns the live run. Keep the job's account, channel, target, and configured delivery route, but do not pass per-call gatewayUrl or gatewayToken fields. Ordinary and standalone message calls may still use those fields. To recover an existing trusted job whose prompt or template supplies them, edit only that prompt or template to strip the two fields, then rerun the same job. A Gateway action reports Scheduled message actions require the active bound Gateway. Remove per-call gatewayUrl and gatewayToken fields and retry. until those fields are gone; absent a scheduler-host binding, it reports Scheduled message actions require an active bound Gateway.. Run the job on its owning Gateway rather than copying connection fields into the prompt. The next send then picks up the live binding, including current cancellation and tool-policy withdrawal.
When an agent builds an isolated reminder from an active chat, OpenClaw saves the preserved live delivery target for the fallback announce route. If it has to recover a saved route, it reads the selected agent's session store, even when the session key is the unqualified global alias. Explicit delivery targets still win. Internal session keys may be lowercase; provider delivery targets are not rebuilt from those keys while current chat context is available.
Implicit announce delivery relies on configured channel allowlists to validate and reroute stale targets. DM pairing-store approvals are not fallback automation recipients; set delivery.to or configure the channel allowFrom entry when a scheduled job should send proactively to a DM.
Owner delivery and direct messages
Set delivery.target: "owner" (--delivery-target owner) when a job should resolve a positively identified owner DM at delivery time. This does not track the last group conversation, nor does it guess a recipient when owner identity is ambiguous. delivery.directPolicy: "block" (--direct-policy block) blocks direct/DM delivery for that job; the default is allow.
A missing owner route does not skip execution. The job still runs, and ordinary delivery settlement records the unavailable destination without falling back to the last conversation. An explicit DM block stays an intentional non-delivery.
openclaw automations edit <job-id> \
--announce --channel telegram --delivery-target owner
These policies cover agent-turn jobs in any supported session, including main, plus non-main command or script jobs with chat delivery. Owner targeting cannot be combined with --to, --thread-id, or --webhook. Enabling it on an existing job clears the previous explicit recipient and thread. Choosing a new explicit recipient replaces owner targeting. Use --clear-delivery-target or --clear-direct-policy to remove the stored policy.
Use payload.includeReasoning: true (--include-reasoning) on an agent-turn job only when reasoning returned by the agent should accompany a meaningful result. Reasoning alone does not create a notification; leaving this setting out keeps ordinary final-output delivery.
Failure notifications
Failure-alert webhooks stay Unknown when the request may have reached the receiver but its response is lost. An explicit HTTP rejection, or a failure proven to occur before sending, records Not delivered and permits the in-app fallback notification, including when Gateway work admission refuses an alert before its transport starts. An unknown outcome does not trigger that fallback.
A model-backed run can finish successfully even when its final reply says the requested task is blocked or could not be completed. The scheduler does not infer execution failure from assistant prose. Such a reply follows normal delivery: announce sends it, none stays silent, and NO_REPLY suppresses a text-only announcement without failing the run. A blocker report does not by itself increment the execution-failure streak, trigger error backoff, or request a failure alert or owner-conversation repair. Runtime errors, such as provider failures and execution timeouts, still follow the failure policy below.
Execution failures share one scheduler-owned threshold and cooldown policy. A job that already has a failure route is covered by default after 2 consecutive failures with a 1-hour cooldown. That route may be a resolved failure destination or the job's primary announce target. Jobs without such a route stay quiet unless a per-job or global failureAlert object explicitly activates the policy.
A timed one-shot job that is disabled after a permanent failure or exhausted retries notifies on that terminal failure without waiting for the consecutive-failure threshold. A watcher-fired on-exit job is terminal on any execution error, transient ones included: a retry would rerun the watched command. Eligible owned jobs get a repair request in their owner conversation; other jobs use the enabled failure-alert route. When no route is enabled, the auto-disable safety notice is sent instead, including with failureAlert: false or inherited best-effort suppression. The shared alert cooldown still applies. Operator cancellations and runs retired by a Gateway restart send no repair, alert, or auto-disable notice.
Repeated failures with the same cause form one incident and do not send repeated alerts, even after the cooldown expires or the Gateway restarts. A changed cause or destination can send a new alert after the cooldown. A successful run clears the incident and its cooldown without sending a notification, so the next failure can alert again; the recovery stays visible in automation history. Skipped runs and unknown delivery outcomes do not establish recovery. A successful quiet trigger check can recover a trigger failure, but cannot establish that a previously failed payload has recovered.
Startup recovery reconciles incidents from saved run outcomes without sending historical notifications. A saved successful run clears the old incident even if its job-state update was interrupted, so a later recurrence can alert again.
Failure notification routes resolve in this order:
- Route fields in the job's
failureAlertobject. job.delivery.failureDestination, layered over the destination fields in globalcron.failureAlert(mode,channel,to,accountId). Acron.failureDestinationblock is not read directly;openclaw doctor --fixmerges it into the global object.- The job's primary announce target.
job.failureAlert: falseturns off execution and required-delivery failure alerts for that job. The auto-disable safety notification still fires.- Global
cron.failureAlert.enabled: falseturns off inherited notifications. A per-jobfailureAlertobject turns that job back on explicitly;enabled: trueexplicitly enables the global policy. - A per-job
failureAlertobject, or any globalcron.failureAlertobject, turns the policy on and tunes it even if the job never had a route before. delivery.bestEffort: truesuppresses inherited/default execution-failure alerts. An explicit per-jobfailureAlertstill takes precedence.delivery.failureDestinationis supported only onsessionTarget="isolated"jobs, unlesswebhookis the primary delivery mode.- Local-provider preflight skips follow the usual failure-alert threshold, incident deduplication, and cooldown, even when
failureAlert.includeSkippedis unset or false. Start the provider or check its endpoint in automation history; recurring jobs stay enabled and resume once a later preflight succeeds. If a skipped one-shot has no scheduled run left, or the job is paused, restore the provider and use Run Now or reschedule the automation. Failure alerts tell these cases apart from jobs that still have another scheduled run. Such skips do not request owner-conversation repair. Explicit alert opt-outs and best-effort policy still apply. failureAlert.includeSkipped: trueopts a job or a global automation alert policy into the other repeated skipped-run alerts. Skipped runs keep a separate consecutive-skip counter, so execution-error backoff is unaffected.openclaw automations editexposes per-job alert tuning:--failure-alert/--no-failure-alert,--failure-alert-after <n>,--failure-alert-channel,--failure-alert-to,--failure-alert-cooldown,--failure-alert-include-skipped/--failure-alert-exclude-skipped,--failure-alert-mode, and--failure-alert-account-id.
In the Control UI, custom failure alerts display the stored threshold, cooldown, and mode overrides. A channel that is omitted shows the neutral last choice without being stored. To fall back to the Gateway's normal global and routing defaults, leave the threshold or cooldown blank, or pick Inherit global setting for alert mode. Cooldowns take decimal seconds at millisecond precision, including 0 for no cooldown; 1.001 seconds, for instance, keeps 1001 milliseconds. Editing other job fields or cloning a job keeps its alert policy, skipped-run setting included.
A required completion-delivery failure differs from an execution failure: a run can record status: "ok" together with completionStatus: "failed". It does not add to the execution-failure streak or backoff. A delivery-failure alert may notify through a resolved alternate failure destination without waiting for failureAlert.after. Repeated delivery failures also collapse into one incident. Alerts for changed failures, including the first delivery failure after an execution alert, respect the shared job/global failureAlert.cooldownMs (default 1 hour); suppressed alerts still leave the delivery failure in run history. Skipped runs and quiet trigger checks do not clear a delivery incident or its cooldown; a successful completion does. For an alert, the scheduler never retries the primary route that already failed.
Chat failure notifications carry the run start time in the agent's configured user timezone. When gateway.publicOrigin is configured and the Control UI is enabled, they also carry an Inspect link to the automation run. Webhook message text stays stable; integrations can read the same instant from the structured runAtMs field and build their own links.
Chat notifications show normalized failure causes, or allowlisted producer facts for known command and script failures. Arbitrary commands, paths, provider bodies, secrets, delivery errors, skip reasons, diagnostics, and stack/error text stay in automation history. Failure webhooks keep the structured raw error for diagnostic integrations.
Owner-conversation repair
Repair is enabled by default. After upgrading, owned automations that alert in chat look different: the first failure alert of a streak turns into a repair request in the conversation that created the job, and the alert becomes the fallback. A job with failureAlert: false gets neither.
When a job created from a conversation (it has an owner session) hits its execution-failure alert threshold and the alert would go to chat (announce mode), OpenClaw sends a repair request to that owner conversation rather than the alert. The request names the job and includes its schedule, name, payload, and last error; the name, payload, and error are marked as untrusted data. Command jobs and on-exit or stream schedules are operator-only and alert as before.
The conversation handles the request as an ordinary agent turn, as if it had received a message: it runs in that conversation's session, with its transcript, workspace, and tool policy, and the reply goes to the conversation's own route, including its thread or topic. Heartbeat settings do not apply. For recurring jobs, a transient outage gets no reply, a problem it can fix in the workspace (for example the helper script or instructions file the job follows) gets fixed with a one-line note, and otherwise it asks you for exactly what it needs. For a terminal one-shot failure, the agent does the still-needed work or schedules a corrected automation, stays silent if the work is no longer needed, or asks you for what is missing. The request does not carry your sender identity, so owner-only tools such as automation control stay unavailable; changing the job itself happens in your reply turn.
The scheduler's repair instructions stay in the model transcript, not the chat feed. Chat shows only the agent's repair note or request for help; pending inputs, live updates, and reloaded history omit the internal request.
Each failure streak gets at most one repair request, even if its cause changes later. The alert is sent as before when the job has no owner conversation or is excluded from repair. If a one-shot's repair request cannot start, the normal failure alert is sent instead. Recurring jobs keep running while the repair turn works. A failed run only verifies the repair if it started after the repair turn finished; earlier runs still count toward the failure streak but do not trigger the unsuccessful-repair alert. If the repair turn exceeds its normal agent timeout, the next failure sends the alert instead. Later alerts in the streak follow the usual cooldown. A successful run clears the streak silently. Existing incidents recorded before settlement tracking was added keep the previous behavior: the next failure after the request can alert.
While a provider network failure, request timeout, overload, rate limit, or server error gets its quick scheduler re-runs (30 seconds, 1 minute, then 5 minutes, or the job's next scheduled run when that comes first), the alert and the repair request wait. The hold covers at most those three re-runs, whatever the schedule, and never applies when the job has no next run (disabled job, finished one-shot, or a schedule with no future slot). If those re-runs also fail, the normal alert or repair follows with the full failure count; if one succeeds, nothing is sent. Failures the job causes itself are not held: its own execution timeout, script failures, and command-job timeouts usually point at the job, so they alert or repair at the normal threshold.
Script setup refreshes retired tools after a plugin reload before execution begins. If that recovery fails, the alert explains that tools could not be refreshed and the script did not run, then points to automation history and plugin status. A monitor that could not run has no new evidence about the system it monitors.
A provider rejection of an unsupported model records model_not_found in the job state and run history. The failure notice points to openclaw doctor --fix for provider-declared retirements, or changing/removing the automation's model override. Known retired automation model routes fail before another inference request. Doctor replaces an override with the provider's declared successor when the agent's model policy allows it. Without a declared successor, it clears the override so the job inherits the agent default. If a pinned override's successor is disallowed, Doctor retains the reference and reports the required policy change. A missing account catalog entry or a discovery outage alone does not authorize a migration.
A safety net is included with the scheduler as well. When a time-based recurring job fails 10 times in a row, it is disabled automatically; any successful execution clears that count. At the point of the final recurring failure, the auto-disable notice goes out straight through the job's failure route: the threshold, cooldown, and any pending repair request do not block it, and neither a repair turn nor any other model turn is involved, so the notice still gets through when the failing model provider is the cause. The notice is passed to the owning agent as a system event only for a job that has no failure route. Schedule-computation errors that repeat trigger auto-disable after 3 errors. Terminal one-shot execution failures also record state.autoDisabled, though no safety notice is added when a repair request or failure alert already owns the notification. The job stores state.autoDisabled.reason as consecutive-failures or schedule-errors, and openclaw doctor reports it. Raw errors remain in automation history. Once the cause is fixed, run openclaw automations enable <jobId>; enabling the job clears the recorded reason and the failure streaks. Since the default list hides disabled jobs, inspect them with openclaw automations list --all. No auto-disable is recorded for operator cancellations, restart retirements, or manual force-runs of paused on-exit jobs.
When a job is linked to the exact conversation that created it, it pauses once that conversation is gone. Should the conversation have been removed or archived, or its bound worktree no longer exist, nothing runs on the next occurrence: no trigger script, payload, or session turn. The job becomes disabled, and its owning agent gets a single notice naming the conversation and the openclaw automations enable <jobId> command. A failure-alert route configured to include skipped runs does not send a second notification. This pause is not an auto-disable, so no state.autoDisabled reason is recorded; the skip reason remains in automation history. Re-enabling does not detach the job, so the next occurrence pauses again while the conversation is still absent. Resetting the conversation with /new does not pause the job, and command jobs never pause. Jobs with no exact conversation link are not affected.
Output language
No reply language is inferred by automation jobs from channel, locale, or earlier messages. State the language rule in the scheduled message or template:
openclaw automations edit <jobId> \
--message "Summarize the updates. Respond in Chinese; keep URLs, code, and product names unchanged."
For template files, keep the language instruction inside the rendered prompt and confirm that placeholders such as {{language}} are filled before the job runs. When output mixes languages, spell the rule out clearly, for example: "Use Chinese for narrative text and keep technical terms in English."