Webhooks
Set up signed AGH webhook triggers for CI, deployment, and external event integrations.
- Audience
- Operators running durable agent work
- Focus
- Automation guidance shaped for scanability, day-two clarity, and operator context.
Webhooks are the external ingress path for automation. They accept signed HTTP requests, normalize
the request into a webhook activation envelope, and dispatch every enabled webhook trigger that
matches the endpoint, scope, workspace, and filters.
There is no separate webhook CLI group. Webhooks are configured as triggers with event = "webhook".
Routes
Webhook endpoint paths include the endpoint slug and webhook ID in one segment:
<endpoint_slug>--<webhook_id>The webhook ID starts with wbh_.
| Scope | Route |
|---|---|
| Global | POST /api/webhooks/global/<endpoint_slug>--<webhook_id> |
| Workspace | POST /api/webhooks/workspaces/<workspace_id>/<endpoint_slug>--<webhook_id> |
AGH returns a result object with the number of matched triggers and the runs created from the delivery.
{
"result": {
"matched": 1,
"runs": [
{
"id": "run_...",
"trigger_id": "trg_...",
"status": "scheduled"
}
]
}
}Configure a webhook trigger
Config-backed webhook triggers need an endpoint slug and a webhook_secret_ref. Use env:NAME for
operator-managed environment variables or a vault:automation/... ref for AGH-managed encrypted
secret storage. AGH resolves the secret only when validating signed webhook requests. Store
AGH-managed values with agh vault put, Settings > Vault, or the write-only API before enabling the
trigger.
[[automation.triggers]]
scope = "workspace"
workspace = "/Users/you/src/checkout-api"
name = "deploy-webhook"
event = "webhook"
endpoint_slug = "deploy"
webhook_secret_ref = "env:AGH_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" }Dynamic webhook triggers can be created with the CLI by passing the secret directly. The secret is write-only through the API surface: AGH uses it for signature validation, but it is not returned by read endpoints.
agh automation triggers create \
--name deploy-webhook \
--scope workspace \
--workspace /Users/you/src/checkout-api \
--event webhook \
--endpoint-slug deploy \
--webhook-secret "$AGH_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:5sRead the trigger to get its webhook path:
agh automation triggers get <trigger-id>Authentication
Every webhook request must include these headers:
| Header | Required | Meaning |
|---|---|---|
X-AGH-Webhook-Timestamp | Yes | Unix seconds or RFC3339 timestamp. Must be within the freshness window. |
X-AGH-Webhook-Signature | Yes | sha256= followed by the HMAC-SHA256 digest. |
X-AGH-Webhook-Delivery-ID | Yes | Idempotency key. Replays are rejected while the delivery ID is inside the freshness window. |
The freshness window is 5m. Requests older than the window, too far in the future, or signed with
the wrong secret are rejected.
The signature message is:
<unix_timestamp>.<raw_request_body>Use Unix seconds for the timestamp header unless your integration has a reason to send RFC3339. When AGH receives RFC3339, it parses the timestamp and validates the signature against the parsed Unix seconds value.
Payload format
Request bodies are limited to 1MiB.
If the body is a JSON object, AGH exposes its fields under .Data for filters and prompt
templates. If the JSON object does not already include payload, AGH also adds the raw request body
as .Data.payload.
AGH adds webhook metadata to every delivery:
| Data key | Meaning |
|---|---|
endpoint | Full endpoint segment. |
endpoint_slug | Human-readable slug from the endpoint. |
webhook_id | Stable webhook ID. |
timestamp | Parsed delivery timestamp. |
If the body is empty or not a JSON object, AGH exposes the raw body as .Data.payload alongside
the metadata fields.
Send a signed request
This example posts a deployment payload to a workspace webhook. It uses openssl to calculate the
HMAC signature.
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 "$AGH_DEPLOY_WEBHOOK_SECRET" -hex | awk '{print $2}')"
curl -sS -X POST "http://localhost:2123/api/webhooks/workspaces/ws_123/deploy--wbh_abc123" \
-H "Content-Type: application/json" \
-H "X-AGH-Webhook-Timestamp: $timestamp" \
-H "X-AGH-Webhook-Signature: $signature" \
-H "X-AGH-Webhook-Delivery-ID: deploy-$timestamp" \
--data "$body"With the trigger above, AGH only dispatches when both filters match:
data.action = deploydata.branch = main
Worked example: CI deployment review
This GitHub Actions step notifies AGH after a deployment workflow reaches the point where an agent should validate the deployment request.
name: Notify AGH deployment automation
on:
workflow_dispatch:
jobs:
notify-agh:
runs-on: ubuntu-latest
steps:
- name: Send signed deployment webhook
env:
AGH_WEBHOOK_URL: ${{ secrets.AGH_WEBHOOK_URL }}
AGH_WEBHOOK_SECRET: ${{ secrets.AGH_WEBHOOK_SECRET }}
BRANCH: ${{ github.ref_name }}
REPOSITORY: ${{ github.repository }}
SHA: ${{ github.sha }}
run: |
body=$(jq -nc \
--arg action deploy \
--arg branch "$BRANCH" \
--arg repository "$REPOSITORY" \
--arg sha "$SHA" \
'{action:$action, branch:$branch, repository:$repository, sha:$sha}')
timestamp=$(date -u +%s)
signature="sha256=$(printf '%s.%s' "$timestamp" "$body" | openssl dgst -sha256 -hmac "$AGH_WEBHOOK_SECRET" -hex | awk '{print $2}')"
curl -sS -X POST "$AGH_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-H "X-AGH-Webhook-Timestamp: $timestamp" \
-H "X-AGH-Webhook-Signature: $signature" \
-H "X-AGH-Webhook-Delivery-ID: deploy-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT" \
--data "$body"Store the full webhook URL in AGH_WEBHOOK_URL, for example:
http://localhost:2123/api/webhooks/workspaces/ws_123/deploy--wbh_abc123Errors
| Status | When AGH returns it |
|---|---|
400 | Invalid endpoint shape, missing required webhook headers, oversized body, or missing webhook secret configuration. |
401 | Stale timestamp, future timestamp, or invalid webhook signature. |
404 | No trigger registration matches the route. |
409 | Replay delivery ID, fire limit, duplicate registration, or read-only definition conflict. |
503 | Automation manager is not running. |
Monitoring deliveries
List failed webhook-triggered runs:
agh automation runs --status failed --last 20Read one run:
agh automation runs get <run-id>Use trigger history when you know which webhook trigger owns the endpoint:
agh automation triggers history <trigger-id> --last 20Related pages
Automation
Triggers
Filter event envelopes and render prompt templates.
Automation
Jobs and Scheduling
Run agents on cron, interval, or one-time schedules.
CLI
automation triggers create
Create the webhook trigger that owns the endpoint.
Vault
Encrypted webhook secrets
Manage redacted `vault:automation/...` metadata and write-only secret values.