Webhook to agent run
Turn a signed HTTP request from CI into an agent run — the trigger, the HMAC signature calculated end to end, and the filters that decide when it fires.
What you build
An endpoint your CI can post to that starts an agent run. A deploy finishes, CI posts a small JSON body, and an agent picks the work up with the commit and branch already in its prompt.
The endpoint is not open. Every delivery carries an HMAC-SHA256 signature over the timestamp and raw body, must arrive inside a 5-minute freshness window, and must bring a delivery id that cannot be replayed while that window is open. Requests that fail any of those are rejected before an agent is ever started.
Use it when an external system already knows something happened and you want Compozy to act on it.
The artifact
The config-backed form defines the trigger that owns the endpoint.
[[automation.triggers]]
scope = "workspace"
workspace = "/Users/you/src/checkout-api"
name = "deploy-webhook"
event = "webhook"
endpoint_slug = "deploy"
webhook_secret_ref = "env:COMPOZY_DEPLOY_WEBHOOK_SECRET"
agent = "deployer"
prompt = "Validate deployment {{ index .Data \"sha\" }} on {{ index .Data \"branch\" }}."
filter = { "data.action" = "deploy", "data.branch" = "main" }
retry = { strategy = "backoff", max_retries = 2, base_delay = "5s" }
fire_limit = { max = 6, window = "1h" }Run it
Before creating the trigger, make sure deployer is an effective agent definition for this workspace
and its provider is configured for the daemon. A trigger stores the agent name, then the dispatcher
resolves it when a delivery creates a session:
compozy agent info deployer --workspace /Users/you/src/checkout-apiFor a custom automation agent, create a workspace-local or global definition with a configured provider before enabling the trigger. See Agent definitions for the resolution cascade.
Pass the secret as a write-only value when creating a dynamic trigger. The CLI stores it in the
Compozy-managed automation vault; it never returns the value. The create response contains the generated
trigger, workspace, endpoint-slug, and webhook IDs. This example uses jq to build the delivery URL from
those machine-readable fields:
export COMPOZY_DEPLOY_WEBHOOK_SECRET="$(openssl rand -hex 32)"
trigger="$(
compozy automation triggers create \
--name deploy-webhook \
--scope workspace \
--workspace /Users/you/src/checkout-api \
--event webhook \
--endpoint-slug deploy \
--webhook-secret-value "$COMPOZY_DEPLOY_WEBHOOK_SECRET" \
--agent deployer \
--prompt 'Validate deployment {{ index .Data "sha" }} on {{ index .Data "branch" }}.' \
--filter data.action=deploy,data.branch=main \
--retry backoff:2:5s \
-o json
)"
trigger_id="$(printf '%s' "$trigger" | jq -er '.id')"
workspace_id="$(printf '%s' "$trigger" | jq -er '.workspace_id')"
endpoint_slug="$(printf '%s' "$trigger" | jq -er '.endpoint_slug')"
webhook_id="$(printf '%s' "$trigger" | jq -er '.webhook_id')"
webhook_url="http://localhost:2123/api/webhooks/workspaces/${workspace_id}/${endpoint_slug}--${webhook_id}"
# inspect the created trigger or save webhook_url in CI secret storage
compozy automation triggers get "$trigger_id" -o jsonPost a signed delivery after the setup block creates webhook_url:
body='{"action":"deploy","branch":"main","repository":"checkout-api","sha":"abc123"}'
timestamp="$(date -u +%s)"
signature="sha256=$(printf '%s.%s' "$timestamp" "$body" \
| openssl dgst -sha256 -hmac "$COMPOZY_DEPLOY_WEBHOOK_SECRET" -hex \
| awk '{print $2}')"
curl -sS -X POST "$webhook_url" \
-H "Content-Type: application/json" \
-H "X-Compozy-Webhook-Timestamp: $timestamp" \
-H "X-Compozy-Webhook-Signature: $signature" \
-H "X-Compozy-Webhook-Delivery-ID: deploy-$timestamp" \
--data "$body"How it works
The signature message is timestamp, dot, raw body
<unix_timestamp>.<raw_request_body>, HMAC-SHA256 with the trigger's secret, hex-encoded, sent
as sha256=…. Sign the raw bytes you actually send — re-serializing the JSON after signing
changes the digest and the delivery is rejected.
Freshness and replay are separate checks
The timestamp must fall inside a 5-minute window in either direction, and
X-Compozy-Webhook-Delivery-ID must not have been seen while that window is open. A retrying
CI job that reuses its delivery id is deduplicated rather than run twice.
The body becomes `.Data`
A JSON object's fields are exposed under .Data for both filters and prompt templates, plus
metadata keys endpoint, endpoint_slug, webhook_id, and timestamp. A body that is not a
JSON object arrives as .Data.payload. Bodies are capped at 1 MiB.
Filters decide before an agent starts
Every filter entry must match exactly. Here the delivery only dispatches when
data.action is deploy and data.branch is main — a staging deploy hits the same endpoint
and is dropped without spending a session.
The secret is write-only
Compozy resolves webhook_secret_ref only when validating a signature. Read endpoints never
return the secret, and neither does this page — env:NAME keeps it with the operator,
vault:automation/... keeps it encrypted in Compozy-managed storage.
Target a Loop instead of a prompt
Swap agent and prompt for a loop_target to start a published Loop. Unlike scheduled jobs,
a trigger has an activation envelope, so input_mapping can route data.<path> values into
the Loop's inputs.
Next steps
Signed webhooks
Routes, headers, error codes, payload limits, and how to monitor deliveries after they land.
Triggers and filters
Every trigger event family, the filter paths, and the activation envelope prompt templates render against.
React to session end
The same trigger machinery driven by an internal lifecycle observer instead of an inbound request.
Vault
Compozy-managed encrypted secret storage behind vault: refs, and how values are written but never read back.
Morning briefing job
A cron job that wakes an agent every weekday morning, has it read the state of your workspace, and leaves a written briefing waiting for you.
React to session end
Watch for sessions that stop with an error and have an agent summarize each one — an event-driven trigger with no clock and no inbound request.