Aegis Orchestrator
Deployment

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:

ProviderHeader to forward
GitHubX-GitHub-Delivery
StripeStripe-Signature (already unique per event)
CustomAny 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-4c9367dc0958

A 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

HTTPCodeCause
401missing_signatureX-Aegis-Signature header absent
400malformed_signatureHeader not in sha256=<hex> format
400invalid_hexHex portion contains non-hex characters
401secret_not_foundNo AEGIS_WEBHOOK_SECRET_<TENANT>_<SOURCE> configured for the resolved (tenant, source) pair
401invalid_signatureHMAC digest does not match
409idempotent_duplicateAlready processed within 24-hour window
422classification_failedRouterAgent confidence below threshold
422no_router_configuredNo 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:

EventPurpose
checkout.session.completedActivates the subscription after successful payment
customer.subscription.updatedPropagates tier upgrades, downgrades, and billing changes
customer.subscription.deletedReverts the user to the Free tier at period end
invoice.payment_succeededConfirms successful renewal payments
invoice.payment_failedMarks 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/stripe

Signature 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:

  1. Verifies the Stripe-Signature header against STRIPE_WEBHOOK_SECRET
  2. Maps the Stripe Price ID from the event to the corresponding ZaruTier
  3. Updates the user's zaru_tier attribute in Keycloak
  4. Returns 200 to 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/git

Set 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

On this page