Slack and Discord Webhooks
Stream Aura events to the channels your team already lives in.
Overview
Aura emits structured events for everything that happens inside the semantic engine: intent logs, impact alerts, zone claims, conflicts, sync pushes, Prove failures, and team messages. The webhook integration delivers these events to Slack and Discord (and any HTTP endpoint speaking the same JSON) in near-real time.
Use webhooks to answer questions like:
- Who is editing what, right now?
- Which intents were logged in the last hour?
- Did anyone's change break a Prove goal on main?
- What functions did my teammates push to the mothership while I was at lunch?
The webhook system is a pure publisher — Aura posts JSON to a URL you control. Authentication, retry logic, and rate limiting are handled by the emitter.
Setup
Slack
- Create a Slack app at
api.slack.com/apps. Enable Incoming Webhooks. - Add a webhook to the target channel (e.g.
#aura-intents). Copy the URL. - Register the webhook with Aura:
aura webhook add \
--name slack-intents \
--url 'https://hooks.slack.com/services/T.../B.../...' \
--events intent.logged,prove.failed,zone.blocked \
--format slack
Verify:
aura webhook test slack-intents
A test message lands in the channel.
Discord
- In Discord, Server Settings → Integrations → Webhooks → New Webhook. Pick a channel. Copy the URL.
- Register with the
discordformat:
aura webhook add \
--name discord-mothership \
--url 'https://discord.com/api/webhooks/.../...' \
--events live.push,live.pull,conflict \
--format discord
The slack and discord formats produce payloads matching each platform's native structure. For generic HTTP, use --format raw.
Hosting
Webhook emission runs in the aura daemon process on each developer's machine and in the mothership server (if you run Mothership). For team-wide events, register the webhook on the mothership:
aura --remote team-main webhook add ...
Individual events will then fire once per team, not once per developer.
Configuration
.aura/webhooks.toml
[[webhook]]
name = "slack-intents"
url_env = "SLACK_INTENT_WEBHOOK" # Read from env var; never hard-code tokens.
events = ["intent.logged", "prove.failed"]
format = "slack"
min_severity = "info"
[[webhook]]
name = "discord-alerts"
url_env = "DISCORD_ALERT_WEBHOOK"
events = ["conflict", "zone.blocked", "impact.new"]
format = "discord"
min_severity = "warning"
[webhook_defaults]
timeout_ms = 3000
retry = { max_attempts = 3, backoff = "exponential" }
rate_limit = { per_minute = 60 }
Always use url_env, not url, in committed config. Webhook URLs are credentials.
Event filters
Filter events with expressions:
[[webhook]]
name = "slack-security"
url_env = "SLACK_SEC_WEBHOOK"
events = ["pr_review.finding"]
filter = "severity == 'error' && category == 'security'"
format = "slack"
Event Types
Aura emits events under these topics. All events share a common envelope:
{
"event": "intent.logged",
"event_id": "evt_01HW3K9Z4X7P2Q8R",
"timestamp": "2026-04-21T14:23:09Z",
"actor": { "id": "ashiq", "kind": "human" },
"repo": "naridon-inc/aura",
"branch": "feat/login-refresh",
"data": { ... }
}
| Topic | When it fires |
|-------|---------------|
| intent.logged | aura_log_intent succeeds. |
| intent.mismatch | Hook detects intent diverging from AST diff. |
| prove.passed | aura prove succeeds for a goal. |
| prove.failed | aura prove fails. |
| pr_review.finding | PR review surfaces a violation. |
| pr_review.done | PR review completes (any outcome). |
| live.push | Functions pushed to mothership. |
| live.pull | Teammate changes pulled and merged. |
| impact.new | Cross-branch impact detected. |
| impact.resolved | Impact alert resolved. |
| zone.claimed | A developer claims a file/function zone. |
| zone.blocked | An edit was blocked by a zone. |
| conflict | AST merge conflict requires human resolution. |
| msg.team | Team message posted. |
| sentinel.msg | AI-agent-to-agent message posted. |
Example Payloads
intent.logged (Slack format)
{
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "Intent logged — feat/login-refresh" }
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Author*\nashiq" },
{ "type": "mrkdwn", "text": "*Nodes*\n3 modified, 1 added" }
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "> Switch linear backoff to exponential for rate-limit compliance"
}
},
{
"type": "context",
"elements": [
{ "type": "mrkdwn", "text": "commit `abc1234` · <https://aura.example.com/i/itn_01HW3K9Z|view>" }
]
}
]
}
prove.failed (raw format)
{
"event": "prove.failed",
"event_id": "evt_01HW3MX8ZQ",
"timestamp": "2026-04-21T14:24:18Z",
"actor": { "id": "ci", "kind": "automation" },
"repo": "naridon-inc/aura",
"branch": "feat/login-refresh",
"data": {
"goal": "Refresh token rotation invalidates old token",
"result": "fail",
"gaps": [
{ "node": "auth::rotate_refresh", "reason": "No edge to token_blocklist::add" }
],
"commit": "abc1234"
}
}
zone.blocked (Discord format)
{
"username": "Aura",
"embeds": [{
"title": "Edit blocked — team zone",
"color": 15158332,
"fields": [
{ "name": "File", "value": "src/billing/invoice.rs", "inline": true },
{ "name": "Owner", "value": "jamie", "inline": true },
{ "name": "Claimed for", "value": "Refactor invoice generation", "inline": false }
],
"footer": { "text": "Message jamie or wait for the zone to release." }
}]
}
impact.new
{
"event": "impact.new",
"data": {
"alert_id": "imp_01HW3P4K",
"your_branch": "feat/login-refresh",
"other_branch": "feat/session-model",
"function": "auth::Session::refresh",
"change": "signature_changed",
"before": "fn refresh(&self) -> Result<Token>",
"after": "fn refresh(&self, ctx: &Context) -> Result<Token>"
}
}
Troubleshooting
No events arriving in Slack. Check the webhook URL isn't expired — Slack rotates them on app re-install. aura webhook test <name> surfaces the HTTP status.
Discord returns 429. Discord rate-limits aggressively (5 req/2s per webhook). Lower rate_limit.per_minute or deduplicate via a dedicated webhook per topic.
Events emitted twice. Two daemons are running (e.g. your laptop + mothership) and both have the same webhook. Register team-wide webhooks only on the mothership; register personal webhooks only on your laptop.
Payloads too large for Slack. Slack caps at 40 KB. For pr_review.done on large PRs, use --compact on the webhook registration to truncate findings to top 10.
Sensitive data in payloads. Use redact_keys in .aura/config.toml to strip fields before emission:
[webhooks.redact]
keys = ["email", "ssh_key", "api_token"]
Thread-Based Routing
For Slack, route related events to the same thread. All events for a given branch can be grouped:
[[webhook]]
name = "slack-branch-threads"
url_env = "SLACK_BRANCH_WEBHOOK"
events = ["intent.logged", "prove.failed", "pr_review.done"]
thread_key = "branch" # new thread per branch; events update the existing one
The first event in a branch opens a thread; subsequent events for the same branch reply to that thread. When the PR merges, Aura posts a final message and archives the thread with a reaction.
Discord has no first-class threads, but Aura simulates thread-like grouping by prefixing messages with the branch name.
Quiet Hours and Digest Mode
Teams get webhook fatigue when every intent fires a notification. Two mitigations:
Quiet hours. Suppress non-critical events during configured windows:
[[webhook]]
name = "slack-intents"
url_env = "SLACK_INTENT_WEBHOOK"
events = ["intent.logged"]
quiet_hours = { tz = "America/New_York", from = "19:00", to = "08:00" }
Events during quiet hours are buffered and delivered at the next active window, collapsed into a single digest.
Digest mode. Batch low-priority events into a single hourly message instead of one per event:
[[webhook]]
name = "slack-digest"
url_env = "SLACK_DIGEST_WEBHOOK"
events = ["intent.logged", "live.push"]
digest = { every = "1h", max_entries = 50 }
The resulting message is a single Slack block summarizing all events in the interval, grouped by author.
Retry and Delivery Semantics
Aura's webhook emitter is designed to be well-behaved:
- At-least-once delivery. If a post fails (network, 5xx), it is retried with exponential backoff up to
retry.max_attempts. Idempotency is up to the receiver; each event has a uniqueevent_idyou can dedupe on. - Ordering is per-topic. Events within the same topic (e.g. all
intent.logged) are delivered in causal order. Events across topics may interleave. - Rate limit aware. On a 429 response, Aura obeys
Retry-Afterand slows subsequent posts to the same endpoint. - Circuit breaker. After 10 consecutive failures to the same webhook, Aura opens a circuit and pauses emission for 5 minutes. Repeated opens over a day auto-disable the webhook and alert via
aura doctor.
For strict ordering and exactly-once semantics, use the HTTP events stream instead — webhooks optimize for simplicity, not durability.
Security: Verifying Webhook Senders
Slack and Discord verify Aura, but you may want to verify Aura verified them. Enable HMAC signing:
[[webhook]]
name = "slack-intents"
url_env = "SLACK_INTENT_WEBHOOK"
hmac_secret_env = "SLACK_INTENT_HMAC"
Each payload gets an X-Aura-Signature: sha256=<hex> header. The receiver recomputes the HMAC over the raw body and compares. This is most useful when the webhook destination is your own service, not Slack or Discord directly.
Multi-Channel Routing
Route events to different channels by severity or topic:
[[webhook]]
name = "slack-low-priority"
url_env = "SLACK_LOW_WEBHOOK"
events = ["intent.logged", "live.push"]
[[webhook]]
name = "slack-alerts"
url_env = "SLACK_ALERT_WEBHOOK"
events = ["prove.failed", "conflict"]
[[webhook]]
name = "slack-security"
url_env = "SLACK_SEC_WEBHOOK"
events = ["pr_review.finding"]
filter = "category == 'security'"
Channel fan-out is done server-side; one event produces up to N HTTP posts.
See Also
- Linear / Jira integration — same event stream, different destination.
- Aura HTTP API — pull events instead of pushing.
- Mothership overview — the server that hosts team-wide webhooks.
- Custom plugins — write a plugin to consume events programmatically.