Configuring Webhooks
Connect external systems to AEGIS workflows using HMAC-authenticated webhooks and direct-route registration.
Configuring Webhooks
Webhooks let external systems — CI/CD pipelines, GitHub, Stripe, any HTTP-capable service — trigger AEGIS workflow executions without a Keycloak account. Authentication is handled by HMAC-SHA256 signature verification, not OAuth.
How It Works
Each webhook source has a secret shared between AEGIS and the sender. When a request arrives at POST /v1/webhooks/{source}, the orchestrator verifies the X-Aegis-Signature header before passing the payload to the routing pipeline.
The {source} path segment is the source name — use a lowercase slug that identifies the system sending the event (e.g. github, stripe, my-ci).
Step 1 — Set the HMAC Secret
Webhook secrets are scoped per (tenant, source) so two tenants registering the same source_name (for example, both calling theirs github) hold completely independent secrets. For each (tenant, source) pair, set an environment variable on the orchestrator:
# Pattern: AEGIS_WEBHOOK_SECRET_<UPPER_TENANT>_<UPPER_SOURCE>
# Hyphens and dots in tenant id and source name are replaced with underscores
export AEGIS_WEBHOOK_SECRET_U_ABC123_GITHUB="your-shared-secret-here"
export AEGIS_WEBHOOK_SECRET_U_ABC123_STRIPE="another-secret"
export AEGIS_WEBHOOK_SECRET_U_DEF456_MY_CI="third-secret"The mapping is:
(tenant=u-abc123, source=github)→AEGIS_WEBHOOK_SECRET_U_ABC123_GITHUB(tenant=u-abc123, source=my-ci)→AEGIS_WEBHOOK_SECRET_U_ABC123_MY_CI(tenant=u-def456, source=my.service)→AEGIS_WEBHOOK_SECRET_U_DEF456_MY_SERVICE
The tenant for an inbound webhook is resolved from the (tenant, source_name) registration written when the webhook was registered. If no environment variable is found for the resolved (tenant, source) pair, the orchestrator returns 401 secret_not_found and the request is rejected.
Step 2 — Register a Direct Route
Map the source to a workflow so routing is instant and deterministic:
Direct-route management is configured via orchestrator workflow routing configuration in the current release (there is no aegis stimulus routes CLI command yet).
If no direct route is registered, the routing pipeline falls back to the configured RouterAgent for LLM-based classification (see Stimulus-Response Routing).
Step 3 — Sign Your Requests
The sender must compute an HMAC-SHA256 signature of the raw request body using the shared secret, then include it in the X-Aegis-Signature header with the prefix sha256=.
Example — Node.js (any sender):
const crypto = require("crypto");
function signPayload(body, secret) {
const sig = crypto
.createHmac("sha256", secret)
.update(body) // body must be the raw bytes/string
.digest("hex");
return `sha256=${sig}`;
}
const body = JSON.stringify({ event: "push", ref: "refs/heads/main" });
const signature = signPayload(body, process.env.WEBHOOK_SECRET);
fetch("https://your-aegis-node/v1/webhooks/github", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Aegis-Signature": signature,
},
body,
});Example — Python:
import hashlib
import hmac
import json
import requests
def sign_payload(body: bytes, secret: str) -> str:
sig = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return f"sha256={sig}"
payload = json.dumps({"event": "push", "ref": "refs/heads/main"}).encode()
signature = sign_payload(payload, WEBHOOK_SECRET)
requests.post(
"https://your-aegis-node/v1/webhooks/github",
data=payload,
headers={
"Content-Type": "application/json",
"X-Aegis-Signature": signature,
},
)Important: Compute the HMAC over the raw request body bytes before any parsing. Computing over a re-serialized object will produce a different signature.
Step 4 — Test the Endpoint
# Generate a test signature
SECRET="your-shared-secret-here"
BODY='{"event":"push","ref":"refs/heads/main"}'
SIG="sha256=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')"
# Send the test webhook
curl -X POST https://your-aegis-node/v1/webhooks/github \
-H "Content-Type: application/json" \
-H "X-Aegis-Signature: $SIG" \
-d "$BODY"A successful response:
{
"stimulus_id": "stim-uuid",
"workflow_execution_id": "wfex-uuid"
}Idempotency
Prevent duplicate workflow executions from retried webhook deliveries by including an idempotency key. Most webhook providers supply one automatically:
| Provider | Header to forward |
|---|---|
| GitHub | X-GitHub-Delivery |
| Stripe | Stripe-Signature (already unique per event) |
| Custom | Any stable per-event UUID |
Forward the provider's delivery ID as the X-Idempotency-Key header on the webhook request. The orchestrator deduplicates on (source_name, idempotency_key) with a 24-hour TTL. Duplicate deliveries return HTTP 409:
{
"error": "idempotent_duplicate",
"original_stimulus_id": "stim-original-uuid"
}Forwarding Headers to the RouterAgent
When Stage 2 LLM classification is used, the orchestrator forwards all request headers to the RouterAgent as part of the stimulus. This lets the router distinguish event types using provider-specific headers:
X-GitHub-Event: pull_request
X-GitHub-Delivery: 72d3162e-cc78-11e3-81ab-4c9367dc0958A well-designed RouterAgent prompt can use X-GitHub-Event to route push events to a deployment workflow, pull_request events to a code-review workflow, and so on.
Error Reference
| HTTP | Code | Cause |
|---|---|---|
401 | missing_signature | X-Aegis-Signature header absent |
400 | malformed_signature | Header not in sha256=<hex> format |
400 | invalid_hex | Hex portion contains non-hex characters |
401 | secret_not_found | No AEGIS_WEBHOOK_SECRET_<TENANT>_<SOURCE> configured for the resolved (tenant, source) pair |
401 | invalid_signature | HMAC digest does not match |
409 | idempotent_duplicate | Already processed within 24-hour window |
422 | classification_failed | RouterAgent confidence below threshold |
422 | no_router_configured | No direct route and no RouterAgent |
POST /v1/stimuli — Keycloak-Authenticated Alternative
Internal services with a Keycloak account can use the authenticated stimulus endpoint instead:
curl -X POST https://your-aegis-node/v1/stimuli \
-H "Authorization: Bearer $KEYCLOAK_JWT" \
-H "Content-Type: application/json" \
-d '{
"source": "http_api",
"content": "Deploy release v2.1.0",
"idempotency_key": "deploy-v2.1.0"
}'Stripe Billing Webhooks
The orchestrator includes a dedicated Stripe webhook handler at POST /v1/webhooks/stripe for processing subscription lifecycle events. This endpoint uses Stripe's native signature verification (HMAC-SHA256 with the Stripe-Signature header) rather than the generic X-Aegis-Signature mechanism.
Required Stripe Events
Configure the following events in your Stripe Dashboard under Developers > Webhooks:
| Event | Purpose |
|---|---|
checkout.session.completed | Activates the subscription after successful payment |
customer.subscription.updated | Propagates tier upgrades, downgrades, and billing changes |
customer.subscription.deleted | Reverts the user to the Free tier at period end |
invoice.payment_succeeded | Confirms successful renewal payments |
invoice.payment_failed | Marks the subscription as past_due for retry handling |
Configuration
Set the webhook signing secret on the orchestrator:
export STRIPE_WEBHOOK_SECRET="whsec_..."Or use the spec.billing.stripe_webhook_secret field in aegis-config.yaml (see Configuration Reference).
Webhook URL
Point your Stripe webhook endpoint to:
https://your-aegis-node/v1/webhooks/stripeSignature Verification
Stripe signs each webhook payload using HMAC-SHA256 with your webhook signing secret. The signature is delivered in the Stripe-Signature header. The orchestrator verifies this signature before processing any event, rejecting requests with invalid or missing signatures.
Tier Propagation
When the orchestrator receives a subscription event, it:
- Verifies the
Stripe-Signatureheader againstSTRIPE_WEBHOOK_SECRET - Maps the Stripe Price ID from the event to the corresponding
ZaruTier - Updates the user's
zaru_tierattribute in Keycloak - Returns
200to Stripe to acknowledge receipt
See Zaru Overview — Subscription Billing for the full tier lifecycle.
Git Repository Webhooks
Git-repo backed persistent volumes expose a separate webhook endpoint that triggers a re-pull of the configured repository. Unlike the generic stimulus webhook, the git webhook authenticates via a per-volume secret carried in a request header — never in the URL path.
Endpoint
POST /v1/webhooks/git
Content-Type: application/json
X-Aegis-Webhook-Secret: <secret>The volume to refresh is identified by the request body. The shared secret is set on the volume when it is created and is verified in constant time against the value supplied in the X-Aegis-Webhook-Secret header. The secret is encrypted at rest via OpenBao Transit; only a SHA-256 lookup hash is stored on the row, and the cleartext is wiped from memory immediately after the request is verified.
Configuration
Configure your git provider (GitHub, GitLab, Gitea, Bitbucket Server, etc.) to send push events to:
https://your-aegis-node/v1/webhooks/gitSet the request to include the header:
X-Aegis-Webhook-Secret: <the secret you configured on the volume>Requests missing the header, presenting a malformed value, or carrying a secret that does not match the registered volume are rejected with 401. The response never echoes the configured secret or its hash.
See Also
- Stimulus-Response Routing — architecture of the routing pipeline
- Building Workflows — define workflows to handle incoming stimuli
- REST API —
POST /v1/webhooks/{source} - REST API — Billing — checkout, portal, subscription, and invoice endpoints
Configuring Storage Volumes
Declaring ephemeral and persistent volumes, volume mounts, access modes, quotas, and managing volumes via CLI.
Configuring Security Contexts
Define named SecurityContext boundaries to control which MCP tools agents can invoke, with path, command, domain, and rate limit constraints.