Webhook Triggers
Generic webhook triggers let external systems submit task context to an agent without a provider-specific integration. Each trigger has its own bearer token, deterministic payload filters, template, agent profile, concurrency policy, and redacted delivery history. For schedule and GitHub triggers, and for asking an agent to set a trigger up, see Triggers.
Create a Trigger
Section titled “Create a Trigger”Open a project, select Triggers, and create a Webhook trigger. Configure:
- An explicit agent profile. Webhook triggers do not fall back to an implicit profile.
- Optionally, a skill to apply as a profile-override layer for triggered runs.
- A prompt template using the webhook context described below.
- Optional
alloranypayload filters. - Optional safe request headers to expose to the prompt.
- The normal trigger concurrency and skip-if-running controls.
SAM shows the raw token only after creation or rotation. Copy it before closing the credential dialog. Later API responses expose only the final four characters. A trigger an agent created never shows its token in the UI: the agent receives a one-time link instead (see Creating Webhooks through MCP), and you can always get a new token with Rotate token on the trigger’s page.
Send a Delivery
Section titled “Send a Delivery”Send a JSON object to the shared ingress endpoint shown with the credential:
The ingress endpoint is https://api.<your-domain>/api/webhooks/ingest — the credential dialog shows the exact URL for your deployment.
curl --request POST 'https://api.your-domain.com/api/webhooks/ingest' \ --header "Authorization: Bearer $SAM_WEBHOOK_TOKEN" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: deployment-1234' \ --data '{"deployment":{"status":"failed","id":"1234"}}'The request must use Content-Type: application/json, and the top-level value must be an object. Put the token in the Authorization header—not the URL or query string. Idempotency-Key is optional; when supplied, it permanently identifies one submission attempt. An identical delivery that received a transient 503 before an execution was linked may retry with the same key. Once linked, SAM never resubmits that key: a durable TaskRunner start is repaired and returned as the linked execution, while a definitively failed attempt keeps returning 503. Use a new key only when you intentionally want a new attempt. Rate-limit and configuration rejections happen before an idempotency reservation, so the same key can be retried after the condition clears. Reusing a key for a different payload is treated as a duplicate.
Successful admission returns HTTP 202. The response indicates whether the delivery created an execution, was filtered, was a duplicate, or was skipped by trigger policy. Invalid credentials return a uniform 404; overload protection can return 429, and a durable submission failure returns 503.
After authentication and delivery reservation, SAM also records a canonical ProjectData event under source webhook with event types such as webhook.accepted, webhook.filtered, webhook.still_running, webhook.concurrent_limit, and webhook.inactive. The event subject is the webhook trigger ID. Metadata includes the delivery ID, trigger identity, optional source label, linked execution ID when one exists, selected safe headers, and a bounded copy of the JSON body with common secret-keyed fields redacted. If immediate ProjectData admission fails, a bounded D1 source intent retries later under the same delivery key instead of resubmitting the task.
Template Context
Section titled “Template Context”Webhook templates can interpolate:
| Variable | Meaning |
|---|---|
{{webhook.payload}} | Canonical compact JSON for the complete object |
{{webhook.body.path.to.value}} | A field from the JSON body |
{{webhook.headers.x-event-type}} | A configured safe request header |
{{webhook.receivedAt}} | ISO 8601 receipt time |
{{webhook.deliveryId}} | Delivery audit identifier |
{{webhook.sourceLabel}} | Optional label configured on the trigger |
{{trigger.id}}, {{trigger.name}} | Trigger identity |
{{project.id}}, {{project.name}} | Project identity |
{{execution.id}}, {{execution.sequenceNumber}} | Reserved execution identity |
Templates are rendered as plain text. Strings keep their original quotes, ampersands, Markdown, and HTML-like characters; SAM does not HTML-entity encode agent prompts. Interpolating a complete object or array, such as {{webhook.body}} or {{webhook.headers}}, produces canonical compact JSON with deterministic object-key ordering. null renders as null, while a missing path renders blank. An optional source label that has not been configured therefore renders blank. If an interpolated value or the complete prompt exceeds its configured bound, the rendered text includes [truncated by SAM] at the truncation point.
Use Preview on the trigger detail page to test a sample payload, header selection, filters, and rendered prompt without starting work. A manual run accepts the same optional sample context but creates a real execution.
Filters and Headers
Section titled “Filters and Headers”Filters use dot-separated object paths and one of three operators:
existsmatches when the path is present. It does not take a value.equalsuses strict comparison against a string, number, boolean, ornull.containschecks a substring in a string or an exact element in an array.
With all mode every filter must match; with any mode at least one must match. Filtered deliveries return 202 and remain visible in delivery history, but they do not create executions.
Only explicitly configured headers are copied into template context. SAM rejects credential- and signature-like names such as Authorization, Cookie, X-API-Key, and headers containing token. This allowlist is for safe metadata such as an event type or tenant ID, not for authenticating another provider’s signature scheme.
Credential Lifecycle and Audit
Section titled “Credential Lifecycle and Audit”Tokens contain 32 random bytes and use the sam_wh_ prefix. SAM stores a keyed hash, never the raw token. Rotating a credential invalidates the previous token immediately and shows the replacement once.
SAM does not retain raw request bodies, arbitrary request headers, bearer tokens, idempotency keys, or rendered prompts in webhook delivery history. The audit contains outcome, HTTP status, byte count, timestamps, and linked execution/error identifiers. Canonical ProjectData webhook events retain only bounded, redacted facts for subscription matching and delivery. Audit records expire automatically; the default retention is seven days.
Disabling or pausing a trigger prevents new webhook work. Best-effort IP and per-trigger request damping reduces bursts; because Cloudflare KV counters are eventually consistent, it is not a strict distributed quota. The shared trigger admission path enforces skip-if-running, concurrency, durable execution reservation, failure tracking, and task submission for cron, GitHub, webhook, and manual sources. A previous run whose task has gone to sleep is not running, so it no longer holds a skip-if-running or concurrency slot; its conversation stays available and a message can still wake it.
Management API
Section titled “Management API”Authenticated project members can use the same management surface as the UI. Project read access permits preview and redacted history; project write access is required to create, update, or rotate credentials:
| Method | Endpoint | Purpose |
|---|---|---|
POST | /api/projects/:projectId/triggers | Create a trigger; webhook creation returns the one-time credential |
PATCH | /api/projects/:projectId/triggers/:triggerId | Update common settings, filters, and included headers |
POST | /api/projects/:projectId/triggers/:triggerId/webhook/preview | Preview filters, context, and rendering |
POST | /api/projects/:projectId/triggers/:triggerId/webhook/rotate | Rotate and return a replacement token once |
GET | /api/projects/:projectId/triggers/:triggerId/webhook/deliveries | Read paginated redacted delivery metadata |
Creating Webhooks through MCP
Section titled “Creating Webhooks through MCP”Use create_trigger with sourceType: "webhook", an explicit project-local agentProfileId, and webhookConfig (for example { "sourceLabel": "build", "filters": [] }). The result includes the trigger ID and webhookClaim containing claimUrl, expiresAt, and the ingress endpoint. The credential never appears in the MCP response.
Redeem from the originating workspace/session with its existing SAM_MCP_TOKEN. No SAM CLI installation is needed. The URL alone grants no access. An authenticated POST succeeds once; GET, HEAD, expired claims, and replays cannot retrieve the token. The default lifetime is ten minutes. Current project write membership is checked again at redemption.
Pipe the response into a secret command accepting stdin. This Bash example keeps authentication out of curl’s process arguments and rejects empty input before updating a Cloudflare Worker secret:
set +xset -o pipefailCLAIM_URL='<webhookClaim.claimUrl>'curl --fail --silent --show-error --request POST \ --config <(printf 'header = "Authorization: Bearer %s"\n' "$SAM_MCP_TOKEN") \ "$CLAIM_URL" | ( IFS= read -r secret || test -n "$secret" [[ "$secret" =~ ^sam_wh_[A-Za-z0-9_-]{43}$ ]] || exit 1 printf '%s' "$secret" | wrangler secret put SAM_WEBHOOK_TOKEN --name my-worker )wrangler needs Cloudflare credentials in the same shell. In a fresh workspace, a variable marked
Secret is only a placeholder there (see Run commands with secrets),
so if that’s how the credential is stored, rotate the token in SAM and set it in the sending service
yourself.
Check that the pipeline succeeded. Do not print the response, use a model-visible fetch tool, enable tracing or HTTP verbosity, or automatically retry redemption. A lost response cannot be recovered: rotate through the UI/REST API, or delete the unused trigger and create a replacement. Rotation invalidates pending claims immediately. The recipient sends Authorization: Bearer <stored token> to the returned ingress endpoint.
This implements webhook delivery; a general sensitive-input or ACP framework remains future work.
Runtime Configuration
Section titled “Runtime Configuration”Self-hosted deployments can tune the ingress body limit, filter limits, rate windows, and audit retention. See the Configuration Reference.
GitHub event triggers
Section titled “GitHub event triggers”Choose GitHub event when creating a repository automation. New issues and pull requests default to opened; issue comments default to created. Push events have no action filter. Empty actions match every action of the selected event. The form and REST/MCP configuration endpoints reject actions that do not belong to that event. See GitHub webhook events and payloads for available actions.
You can edit an existing GitHub trigger’s event and filters. Changing the event resets its action default, retains compatible labels/text/branches, and removes filters that do not apply. Required labels match all configured labels; text matching is case-insensitive; source branch matching is case-sensitive. Pull request branches refer to the source branch. Push branch filters also accept full tag refs such as refs/tags/v1. Clearing the draft checkbox includes draft pull requests.
Resuming a paused trigger starts a new failure window while keeping execution history. Skipped deliveries do not erase consecutive failures. GitHub deliveries are deduplicated by delivery ID; GitHub does not automatically retry failed webhook deliveries. SAM currently acknowledges GitHub webhooks before asynchronous routing completes, so an acknowledged delivery that fails after being claimed cannot be recovered by simply redelivering its same ID.