Skip to content

Events & Webhooks

Events connect external sources to mecha workers. Sources can be passive (webhooks from GitHub, GitLab, or custom services) or active (cron schedules, API polling). When an event arrives, mecha matches it to a worker, renders a prompt, dispatches a task, and writes the result back.

Universal Event Model

Every event — regardless of source — has the same shape:

FieldDescriptionExample
SourceProvider namegithub, gitlab, slack, cron
TypeEvent typepull_request.opened, push, message, tick
ActorWho triggered itusername, login, bot name
SubjectWhat it's aboutowner/repo, #channel, schedule name
AttrsProvider-specific datarepo_owner, number, diff, text
DedupKeySemantic dedup key (enforced)Content hash for polls/cron — active events block duplicates

Actor and Subject are universal — available for all sources. Attrs keys are provider-specific (e.g., repo_owner exists for GitHub/GitLab but not for a custom webhook).

Provider Interfaces

InterfaceDirectionDescription
SourceInbound (passive)Parses webhooks into events (Parse)
TriggerInbound (active)Generates events on schedule or poll (Start)
HydratorEnrichmentFetches additional data via API (e.g., PR diffs)
VerifierHandshakeHandles webhook verification challenges (Meta, Slack)
AuthenticatedMarkerSources with built-in auth (HMAC, token) skip API key check
ResponderOutboundWrites results back to the source platform

GitHub and GitLab implement Source + Hydrator + Authenticated. Slack and Telegram implement Source + Authenticated. Generic sources implement only Source.

Pipeline

Setup

1. Configure Secrets

Add your GitHub token and webhook secret to ~/.mecha/secrets.yml:

yaml
github:
  token: ghp_your_github_pat
  webhook_secret: whsec_your_webhook_secret
  • token — GitHub PAT for API calls (diff fetching + write-back)
  • webhook_secret — HMAC secret for webhook signature verification

2. Add Event Rules to Workers

Add an events: section to your worker YAML:

yaml
name: pr-reviewer
docker:
  image: mecha-worker:latest
  token: claude.xiaolaidev
  env:
    CLAUDE_MODEL: claude-sonnet-4-6
    CLAUDE_EFFORT: high
timeout: 30m

events:
  - source: github
    on:
      - pull_request.opened
      - pull_request.synchronize
    filter:
      base_branch: main
    prompt: |
      Review this pull request for security and correctness.

      ## PR #{{.number}}: {{.title}}
      Author: {{.actor}}
      Branch: {{.head_branch}} -> {{.base_branch}}

      ### Diff
      {{.diff}}

3. Configure GitHub Webhook

In your GitHub repo settings → Webhooks:

  • Payload URL: https://your-server/webhook/github
  • Content type: application/json
  • Secret: same as webhook_secret in secrets.yml
  • Events: Select the events matching your worker rules

4. Start the Server

bash
mecha serve --addr 0.0.0.0:21212 --api-key YOUR_API_KEY

Event Rules

Each rule in the events: section defines when the worker should handle an event:

FieldRequiredDescription
sourceYesEvent source (github, gitlab, or custom name)
onYesList of event types to match
filterNoKey-value payload filters (equality match)
promptYesGo template rendered with event data
autoNoAuto-dispatch (default: true). Set false to match without dispatching

GitHub Event Types

TypeTrigger
pull_request.openedPR created
pull_request.synchronizePR updated (new commits)
pull_request.closedPR closed/merged
pushPush to branch
issues.openedIssue created
issues.labeledLabel added to issue
issue_comment.createdComment on issue/PR
pull_request.review_requestedReview requested

Template Variables

Available in the prompt template:

Universal fields (available for all sources):

VariableDescription
{{.actor}}Who triggered the event (username, login, phone)
{{.subject}}What it's about (owner/repo, #channel, schedule name)
{{.source}}Provider name (github, gitlab, custom)
{{.type}}Event type (pull_request.opened, push, message)

GitHub/GitLab attrs (source-specific, from Attrs map):

VariableDescription
{{.repo_owner}}Repository owner
{{.repo_name}}Repository name
{{.number}}PR/issue/MR number
{{.sender}}Who triggered (GitHub-specific alias for actor)
{{.title}}PR/issue title
{{.body}}PR/issue body
{{.diff}}PR diff (fetched via API, max 500KB)
{{.file_list}}Changed file names
{{.head_branch}}Source branch
{{.base_branch}}Target branch
{{.head_sha}}Head commit SHA
{{.labels}}Comma-separated label names

Write-Back (Responder)

When a worker returns a result with write-back fields, mecha routes them through the Responder registered for the event's source. The GitHub Responder posts comments, labels, and statuses to the GitHub API. Custom Responders can be registered for other platforms (Slack, Telegram, etc.).

json
{
  "output": "Found 2 security issues...",
  "comment": {
    "target": "pr:42",
    "body": "## Security Review\nFound 2 issues..."
  },
  "status": {
    "state": "failure",
    "description": "2 security issues found"
  },
  "labels": {
    "add": ["security-review-failed"],
    "remove": ["needs-review"]
  }
}
FieldGitHub Action
comment.bodyPosts PR/issue comment
status.stateSets commit status (success/failure/pending)
labels.addAdds labels to PR/issue
labels.removeRemoves labels from PR/issue

Write-back requires github.token in secrets.yml with appropriate permissions.

Event States

  • received → failed: on startup, events stuck in received (crashed before matching) are re-processed if the source is still registered, or marked failed if the source is gone
  • dispatched → dispatched: if write-back fails but the task completed, the event stays dispatched (not failed) so it can be retried

Delivery Deduplication

Each event source uses its own deduplication strategy:

SourceDedup keyMethod
GitHubX-GitHub-Delivery headerUnique per delivery
GitLabX-Gitlab-Event-UUID header (preferred), falls back to X-Gitlab-Instance/X-Request-IdUnique per delivery
Slackevent_id from payloadUnique per event
TelegramContent hash (SHA-256 of body)Deterministic
GenericContent hash (SHA-256 of event type + body)Deterministic
CronFNV hash of (name, subject, unix_time)Prevents duplicate ticks

Additionally, events with a non-empty DedupKey are blocked if an active event (received, matched, or dispatched) with the same key already exists. Terminal states (completed, failed, skipped) release the key.

GitLab Source

Add gitlab.webhook_secret to ~/.mecha/secrets.yml:

yaml
gitlab:
  webhook_secret: your_gitlab_secret

GitLab Event Types

TypeTrigger
merge_request.openMR created
merge_request.updateMR updated
merge_request.mergeMR merged
pushPush to branch
tag_pushTag created
noteComment on MR/issue
issue.openIssue created

GitLab Worker Example

yaml
events:
  - source: gitlab
    on:
      - merge_request.open
    prompt: "Review MR #{{.number}}: {{.title}}"

GitLab Write-Back

The GitLabResponder writes task results back to GitLab merge requests and issues. It is registered as a Responder and is keyed by ev.Source (so GitLab events automatically use it).

Capabilities:

Result fieldGitLab action
comment.bodyPosts a note on the MR or issue
status.stateSets commit status on head_sha (context name: mecha)
labels.addAdds labels to the MR or issue
labels.removeRemoves labels from the MR or issue
commit.diffPosts a note with the suggested diff as a markdown code block

State mapping (GitHub to GitLab):

GitHub stateGitLab state
errorfailed
failurefailed
pendingpending
successsuccess

Configuration:

The GitLab Responder requires a GitLab Personal Access Token (PAT) with api scope, separate from the gitlab.webhook_secret used for webhook verification. Create the responder with:

go
resp := source.NewGitLabResponder("https://gitlab.com/api/v4", "glpat-xxx...")
registry.RegisterResponder(resp)

The project path (owner/repo) is extracted from event attrs and URL-encoded automatically (owner/repo becomes owner%2Frepo in API calls).

INFO

Commit status requires the event to have a head_sha attr. If head_sha is empty, the status update is silently skipped.

Slack Source

Add slack.signing_secret to ~/.mecha/secrets.yml:

yaml
slack:
  signing_secret: your_slack_signing_secret

Slack webhooks are verified via HMAC-SHA256 (v0= scheme) with replay protection (5-minute timestamp window). URL verification challenges are handled automatically.

Slack Event Types

TypeTrigger
messageMessage posted in channel
message.bot_messageBot message
app_mentionBot mentioned
reaction_addedEmoji reaction

Slack Template Variables

VariableDescription
{{.channel}}Channel ID
{{.text}}Message text
{{.sender}}User ID
{{.thread_ts}}Thread timestamp
{{.ts}}Message timestamp
{{.team_id}}Workspace ID

Slack Worker Example

yaml
events:
  - source: slack
    on:
      - message
    prompt: "Respond to this Slack message: {{.text}}"

Telegram Source

Add telegram.secret_token to ~/.mecha/secrets.yml:

yaml
telegram:
  secret_token: your_telegram_secret_token

Telegram webhooks are verified via X-Telegram-Bot-Api-Secret-Token header (constant-time comparison).

Telegram Event Types

TypeTrigger
messageNew message
edited_messageMessage edited
callback_queryInline button pressed
inline_queryInline query

Telegram Template Variables

VariableDescription
{{.text}}Message text
{{.chat_id}}Chat ID
{{.chat_type}}private, group, supergroup, channel
{{.sender}}Username
{{.from_id}}User ID
{{.message_id}}Message ID
{{.callback_data}}Callback button data

Telegram Worker Example

yaml
events:
  - source: telegram
    on:
      - message
    prompt: "Reply to this Telegram message: {{.text}}"

Cron Trigger

The cron trigger generates events on a fixed schedule. Unlike webhook sources (which are passive), CronTrigger is an active trigger that emits events at a configurable interval.

Cron triggers are registered programmatically -- not via secrets.yml. They implement the Trigger interface and are started by mecha serve.

How It Works

NewCronTrigger(name, interval, subject) creates a trigger that fires every interval. When it fires, it emits an event with these fields:

FieldValue
Sourcecron
Typetick
ActorTrigger name (the name argument)
SubjectThe subject argument
DedupKeyFNV-64a hash of name:subject:unix_time

The DedupKey is computed as an FNV-64a hash of the trigger name, subject, and Unix timestamp. This prevents duplicate processing if the emit callback is slow -- a tick that has already been emitted (and is still active) will be blocked by dedup enforcement.

Template Variables

VariableDescription
{{.tick_time}}RFC 3339 timestamp of when the tick fired
{{.actor}}Trigger name
{{.subject}}Trigger subject

Cron Worker Example

yaml
events:
  - source: cron
    on:
      - tick
    prompt: |
      Run the scheduled task at {{.tick_time}}.
      Schedule: {{.actor}} / {{.subject}}

Generic Webhook Source

For custom integrations (Jenkins, Buildkite, etc.), register a generic source. The event type is read from a configurable HTTP header.

Generic sources are registered programmatically (not via secrets). They use content-hash deduplication and have no built-in authentication — use the server's API key for access control.

Generic Worker Example

yaml
events:
  - source: jenkins
    on:
      - build.completed
    filter:
      status: "failure"
    prompt: "Analyze this build failure: {{.branch}}"

Commit Suggestions

Workers can return a commit field with suggested code changes. Mecha posts the diff as a PR comment:

json
{
  "output": "Fixed the typo",
  "commit": {
    "message": "fix: correct variable name",
    "diff": "--- a/main.go\n+++ b/main.go\n@@ -1 +1 @@\n-old\n+new"
  }
}

The diff is rendered as a markdown code block in the PR comment. Requires policy.commit.allow: true in the worker config.

Security

  • GitHub webhooks verified via HMAC-SHA256 (constant-time comparison)
  • GitLab webhooks verified via X-Gitlab-Token (constant-time comparison)
  • Slack webhooks verified via HMAC-SHA256 (v0= scheme) with 5-minute replay protection
  • Telegram webhooks verified via X-Telegram-Bot-Api-Secret-Token (constant-time comparison)
  • Authenticated sources (GitHub, GitLab, Slack, Telegram) are exempt from API key auth — they use their own verification. Generic sources without built-in auth must supply the server API key
  • Diff is fetched using SHA-pinned compare endpoints (immutable)
  • Delivery deduplication prevents replay attacks

Released under the ISC License.