Event Triggers
Event Triggers
Section titled “Event Triggers”Event triggers let an external system start agent work: when a provider event arrives and a rule matches it, Dewee records a durable dispatch, runs the rule’s agent with its task, and can report the result back to the provider. GitHub issue and pull request label events are the first supported source.
Event triggers are not the Webhook API. /v1/webhooks is for your own systems calling an agent directly. Event triggers receive provider events, check them against rules and policy, and turn each match into a dispatch that you can track, cancel and retry.
Concepts
Section titled “Concepts”| Concept | Meaning |
|---|---|
| Source | A tenant’s connection to a provider, for example one GitHub webhook. Holds an encrypted signing secret, an optional encrypted outbound token, and the provider config (such as the allowed repositories). |
| Rule | Belongs to a source. Event type, optional subject type and match expression, the agent that runs, the task template, session mode, policy and on-complete action. |
| Event | One authenticated provider delivery, normalized and stored in the inbox. The raw provider payload is not stored, only its SHA-256. |
| Dispatch | The decision for one event and rule: queued, running, then succeeded, failed, interrupted, cancelled or dead; or skipped with a reason. |
The gateway answers the provider only after the event and its dispatches are stored. Agents never run inside the webhook request. A redelivery of the same delivery is recognized and creates nothing new. Only one run of an agent on one resource (for example one issue) happens at a time; different resources run in parallel.
A run that is cut off by a crash or a restart becomes interrupted and is not re-run automatically, because its tools may already have had side effects. Retry it explicitly.
GitHub setup walkthrough
Section titled “GitHub setup walkthrough”Event triggers need GOCLAW_ENCRYPTION_KEY on the gateway; without it the routes are not mounted. Every management step needs a tenant admin.
1. Create a source
Section titled “1. Create a source”Create a GitHub source with the repositories it may accept events from:
dewee triggers sources create --provider github --name acme --repo acme/apiThe response shows the webhook secret once and the webhook path /v1/integrations/github/webhook/<source-id>. Copy the secret now. If you lose it, rotate it (step 6).
To let Dewee comment the outcome on the issue or pull request, add a fine-grained GitHub token with Issues: Read and write and Pull requests: Read and write on the allowed repositories. Pass it on stdin so it stays out of shell history:
printf '%s' "$GITHUB_TOKEN" | dewee triggers sources create --provider github --name acme --repo acme/api --credential-stdinThe token is encrypted at rest and never returned. It is also what Dewee uses to check the sender’s repository permission (see Security).
2. Configure the webhook in GitHub
Section titled “2. Configure the webhook in GitHub”In the GitHub repository, open Settings → Webhooks → Add webhook and set:
- Payload URL: your gateway’s public origin followed by the webhook path from step 1.
- Content type:
application/json. - Secret: the webhook secret from step 1.
- Events: choose “Let me select individual events” and tick Issues and Pull requests.
GitHub sends a ping first; the gateway answers 200.
3. Create a rule
Section titled “3. Create a rule”dewee triggers rules create --source <source-id> --name "AI handle" --subject-type issue \ --match "data.label == 'ai-handle'" --agent coding-agent \ --task "Implement the issue" --on-complete source.comment--agent takes an agent id or agent_key. --match is a CEL expression; leave it out to match every event of the type. Policy flags such as --max-duration, --max-per-hour and --allowed-actor are listed in the CLI reference.
4. Test the rule without dispatching
Section titled “4. Test the rule without dispatching”Write a canonical event to event.json:
{ "event_type": "resource.label.added", "subject": { "type": "issue", "id": "I_test", "scope": "acme/api", "ref": "#42" }, "actor": { "type": "external_user", "id": "1", "name": "octocat" }, "data": { "label": "ai-handle" }}Then dry-run it:
dewee triggers rules test <rule-id> --event-file event.jsonThe result shows whether the rule matched and the decision (queued, skipped with a reason, or no_match). A dry run stores and dispatches nothing, and it does not check the hourly rate limit, coalescing or the sender’s GitHub permission.
5. Trigger it for real
Section titled “5. Trigger it for real”Add the ai-handle label to an issue in acme/api, then follow the dispatch:
dewee triggers events list --source <source-id>dewee triggers dispatches list --status runningdewee triggers dispatches get <dispatch-id>The dispatch shows its run id, trace id and outcome. Open the trace with dewee traces get <trace-id> or in Traces in RGD or CCP.
6. Rotate or revoke
Section titled “6. Rotate or revoke”dewee triggers sources rotate-secret <source-id> issues a new secret; update it in GitHub straight away. dewee triggers sources disable <source-id> or delete revokes the source.
Supported GitHub events
Section titled “Supported GitHub events”| GitHub event | Result |
|---|---|
| issues or pull_request with action labeled | Event type resource.label.added, subject type issue or pull_request |
| ping | 200, nothing stored |
| Anything else | 202 with status ignored, nothing stored |
Match expressions can use event, source, subject, actor, data (including data.label, data.title and data.body) and meta. Example: data.label == "ai-handle" && subject.scope == "acme/api".
Policy defaults
Section titled “Policy defaults”Each rule has a policy. Omitted fields use these defaults:
| Field | Default | Bounds | Meaning |
|---|---|---|---|
| max_duration_seconds | 1800 | 60–7200 | Wall-clock budget of the agent run. |
| max_iterations | 0 | 0–200 | Tool-loop iteration cap; 0 uses the agent’s own limit. |
| max_attempts | 3 | 1–10 | Attempts to start the run before the dispatch becomes dead. |
| on_active | coalesce | coalesce or queue | When several dispatches for one rule and resource wait behind a running one: keep only the newest, or run all in order. |
| max_dispatches_per_hour | 10 | 1–100 | Per rule and resource. Extra dispatches are skipped as rate_limited, which stops trigger loops. |
| allowed_actors | empty | up to 50 | If set, only these actor names or ids may trigger the rule. |
| allow_bot_actors | false | | Bot senders are refused unless enabled. |
Session modes: per_resource (default; follow-up events on the same issue continue the same conversation) or isolated (a fresh session per dispatch).
When you update a rule’s policy, send only the keys you want to change; null resets a key to its default.
Security
Section titled “Security”- Signed deliveries only. The
X-Hub-Signature-256signature is verified with the source secret. A bad signature gets401and creates nothing. Unknown or disabled sources get404. - Repository allowlist. Events from repositories outside the source’s list, or from senders in the source’s ignore list, are stored as
rejectedand dispatch nothing. - Sender permission is checked with GitHub. Issue forms can add labels on behalf of any issue author, so a label alone proves nothing. Before each run Dewee asks GitHub for the sender’s permission and requires
triage,write,maintainoradmin. It also refuses events sent by the token’s own account. Without a token this check cannot run, so dispatches are skipped unless the rule listsallowed_actors. - Issue text is untrusted. Issue and pull request text reaches the agent only inside a fenced, length-capped untrusted-content block. It cannot change the agent’s tools, permissions, tenant or repository scope; the run uses the agent’s own tool policy and approval gates.
- Rate limit. The receiver accepts 1,200 deliveries per minute from one sender address across all sources, and 600 per minute per source from one address. Over either limit it answers
429withRetry-After: 60. An IPv6 sender is counted per/64.
API routes
Section titled “API routes”Management routes need a tenant admin and act in the caller’s tenant. Request and response schemas are in the Interactive API Console under the Event Triggers tag.
| Method and path | Purpose |
|---|---|
| GET /v1/event-triggers/providers | Providers with their event kinds and outcome actions |
| GET, POST /v1/event-triggers/sources | List or create sources (create returns the secret once) |
| GET, PATCH, DELETE /v1/event-triggers/sources/{id} | Show, update (name, status, config, token) or delete a source |
| POST /v1/event-triggers/sources/{id}/rotate-secret | Issue a new webhook secret |
| GET, POST /v1/event-triggers/rules | List (filter by source_id) or create rules |
| GET, PATCH, DELETE /v1/event-triggers/rules/{id} | Show, update (including enabled) or delete a rule |
| POST /v1/event-triggers/rules/{id}/test | Dry-run an event against a rule |
| GET /v1/event-triggers/events | Event inbox |
| GET /v1/event-triggers/events/{id} | One event with its dispatches |
| GET /v1/event-triggers/dispatches | Dispatches |
| GET /v1/event-triggers/dispatches/{id} | One dispatch with run id, trace id and outcome |
| POST /v1/event-triggers/dispatches/{id}/cancel | Cancel a queued dispatch or stop a running one |
| POST /v1/event-triggers/dispatches/{id}/retry | Re-queue a failed, interrupted, cancelled or dead dispatch |
| POST /v1/integrations/{provider}/webhook/{source_id} | Public receiver, authenticated by the provider signature |
List routes page with limit (default 50, maximum 200) and offset. There is no total count, so keep paging until a page comes back short.
dewee triggers covers every management route. All commands accept --json or -o json, and destructive commands ask for confirmation or take --force.
dewee triggers providersdewee triggers sources listdewee triggers sources enable <id>dewee triggers rules list --source <source-id>dewee triggers rules disable <rule-id>dewee triggers events list --status rejecteddewee triggers events get <event-id>dewee triggers dispatches list --status faileddewee triggers dispatches retry <dispatch-id>dewee triggers dispatches cancel <dispatch-id>Every flag is in the CLI reference.
In the web apps
Section titled “In the web apps”- RGD (Runtime Gateway Dashboard): sidebar System → Event Triggers, with Sources, Rules and Activity tabs. Visible to admins.
- CCP (Customer Control Plane): Connect → Event triggers in your workspace, with the same three tabs.
Both show the webhook secret once after you create a source or rotate its secret, let you test a rule, and show each event’s dispatches with their reasons.
Troubleshooting
Section titled “Troubleshooting”Delivery responses
Section titled “Delivery responses”| What you see | Meaning |
|---|---|
| 401 in GitHub’s delivery log | The signature did not verify. The secret in GitHub does not match; paste the current one or rotate it. |
| 404 | The source id is unknown, or the source is disabled. |
| 429 | More than 600 deliveries per minute for this source, or 1,200 for all sources, from one address. |
| 500 | The gateway could not store the delivery. GitHub does not resend it on its own; see below. |
| 202 with status ignored | Not an issues or pull_request event, or an action other than labeled. Nothing is stored. |
| 400 | The payload is malformed, for example not JSON or missing the repository, sender or label. |
| 200 with status duplicate | GitHub redelivered a delivery that was already stored. |
| Event accepted but no dispatch | No enabled rule matched the event type, subject type and match expression. Non-matching rules are not recorded; use rules test to check. |
GitHub does not redeliver a failed delivery automatically. After a gateway outage, open the repository’s webhook settings, go to Recent Deliveries, and redeliver the failed ones. Redelivery is safe: a delivery that was already stored answers duplicate and creates nothing new.
Reason codes
Section titled “Reason codes”Find the reason with dewee triggers events get <event-id> or dewee triggers dispatches get <dispatch-id>, or on the Activity tab.
| Reason | Where | What to do |
|---|---|---|
| repository_not_allowed | Event rejected | Add the repository to the source’s allowed list. |
| actor_ignored | Event rejected | The sender is on the source’s ignore list. |
| actor_unverified | Dispatch skipped | The source has no GitHub token, so the sender’s permission cannot be checked. Add a token or set allowed_actors on the rule. |
| actor_not_authorized | Dispatch skipped | The sender lacks triage or higher on the repository. |
| self_actor | Dispatch skipped | The event was sent by the token’s own account. |
| actor_not_allowed | Dispatch skipped | The sender is not in the rule’s allowed_actors. |
| bot_actor | Dispatch skipped | A bot sent the event. Set allow_bot_actors if that is intended. |
| actor_check_failed | Dispatch re-queued | GitHub could not be reached for the permission check; retried within max_attempts. |
| rate_limited | Dispatch skipped | The rule hit max_dispatches_per_hour for this resource. |
| superseded | Dispatch skipped | A newer dispatch for the same rule and resource replaced it (on_active: coalesce). |
| match_error | Dispatch skipped | The match expression failed at runtime. Fix the expression and test the rule. |
| hop_limit | Dispatch skipped | The event was caused by too long a chain of earlier triggers. |
| target_unavailable | Dispatch skipped or failed | The rule’s agent no longer exists or cannot be resolved. |
| policy_invalid | Dispatch skipped or failed | The stored policy could not be read. Update the rule’s policy. |
| agent_unavailable | Dispatch dead | The agent could not be started within max_attempts. Retry once it is available. |
| timeout | Dispatch failed | The run exceeded max_duration_seconds. |
| run_error | Dispatch failed | The agent run ended with an error. Open its trace. |
| cancelled_by_operator | Dispatch cancelled | Someone cancelled it. |
| no_credential | Outcome failed | The rule posts a comment but the source has no token. |
| outcome_error | Outcome failed | The comment could not be posted; the detail follows the code. |
Each dispatched run is a normal agent trace named Event trigger [<rule>] - <agent> and tagged event-trigger, so the full run is in Traces in RGD and CCP and in dewee traces get <trace-id>.