Skip to main content
Guide

Make dynamic egress decisions with a webhook

Attach a webhook to an egress rule so an external endpoint decides at request time how to handle outbound traffic. Use it for tenant-specific authorization, runtime token minting, or dynamic upstream routing that a static rule cannot express.

An egress rule normally carries a static action. Replace it with a hookRef and, when the rule matches, the egress proxy calls your endpoint and applies the decision it returns. A rule has either action or hookRef, never both.

Read Control egress first — a webhook is an advanced field on the same policy file. The webhook is called whenever a request matches the rule, including under Partial traffic inspection; you do not need Full inspection to use it.

Add a webhook rule

A hookRef rule declares the endpoint to call, what to send it, and how to authenticate. This example calls a policy service for every chat completion and applies whatever decision it returns.

defaultAction: Deny
trafficInspection: Full
rules:
- name: dynamic-copilot-policy
match:
host: "*.githubcopilot.com"
path: "/chat/completions"
hookRef:
endpoint: "https://policy.example.net/egressWebHook"
failBehavior: Deny
requestHeaders:
- x-tenant-id
- x-session-id
authHeaders:
- operation: Set
name: Authorization
format: "Bearer {value}"
valueRef:
managedIdentityRef:
resource: "api://policy-gateway"
type: SystemAssigned
FieldSupported values
hookRef.endpointRequired HTTPS URL the proxy calls when the rule matches.
hookRef.failBehaviorThe decision applied when the call fails, times out, or returns an invalid response. Deny (default) or Allow.
hookRef.requestHeadersRequest header names forwarded to the webhook. Only these headers are sent, and their names are lower-cased in the payload.
hookRef.authHeadersHeaders the proxy adds to the webhook call to authenticate to your endpoint. Each entry has operation (Set, Insert, Remove), name, an optional format (with {value} as the placeholder), and a value or valueRef.
authHeaders[].valueRefmanagedIdentityRef (resource, type), secretRef, or a static value — the same value sources used by header transforms.
hookRef.timeoutMsOptional timeout in milliseconds for the webhook call.
hookRef.routingModeHow the proxy reaches the webhook endpoint. Default uses the sandbox's routing, Vnet binds the call to the sandbox's VNet IP so the endpoint can be a private service, and Platform forces the node's platform egress.

Rules are still evaluated in order, first match wins. The webhook is called only when its rule is the first to match.

What the webhook receives

When the rule matches, the proxy sends a POST to hookRef.endpoint with this body:

{
"apiVersion": "adc.egress.hook/v1",
"sandboxId": "...",
"armSandboxGroupResourceId": "/subscriptions/.../resourceGroups/.../providers/Microsoft.App/sandboxGroups/...",
"request": {
"protocol": "http",
"scheme": "https",
"host": "api.example.com",
"path": "/v1/chat/completions",
"method": "POST",
"headers": {
"x-tenant-id": "tenant-123",
"x-session-id": "session-abc"
}
}
}
FieldMeaning
apiVersionContract version. Currently adc.egress.hook/v1.
sandboxIdThe sandbox that made the request.
armSandboxGroupResourceIdThe ARM resource ID of the sandbox group, when the sandbox is associated with one. Omitted when unavailable. It identifies the group to your receiver but does not affect ADC evaluation, routing, or whether the hook is called.
request.protocolhttp or tds. It tags which fields are populated.
request.schemehttp or https. Populated for http.
request.hostDestination host of the outbound request. Populated for http.
request.pathRequest path. Populated for http.
request.methodHTTP method. Populated for http.
request.headersOnly the headers named in hookRef.requestHeaders, with lower-cased names. Populated for http.

The same endpoint can serve SQL (TDS) traffic. For a tds request the proxy populates server, database, and login instead of the HTTP fields.

The authHeaders you configured are added to this request so your endpoint can authenticate the caller. They are not part of the JSON body.

What the webhook returns

Return a decision the proxy applies to the outbound request:

{
"decision": {
"type": "Rewrite",
"headers": [
{
"operation": "Set",
"name": "Authorization",
"value": "Bearer <token>"
}
],
"scheme": "https",
"host": "api.openai.com",
"path": "/v1/responses"
},
"reason": "tenant override",
"cacheTtlSeconds": 5,
"cacheKey": {
"fields": ["Method"],
"headers": ["x-tenant-id"]
}
}
FieldMeaning
decision.typeAllow, Deny, Transform, or Rewrite.
decision.headersHeader mutations for Transform or Rewrite. Each has operation (Set, Insert, Remove), name, and value. Only literal values are accepted — secret and managed-identity references are not resolved from a webhook response.
decision.schemeRewrite target scheme. Valid for Rewrite.
decision.hostRewrite target host. Valid for Rewrite.
decision.pathRewrite target path. Valid for Rewrite. Query strings from the original request are preserved.
decision.routingModeOptionally overrides how the proxy reaches the rewritten upstream. Default, Vnet, or Platform. Valid for Rewrite.
decision.trustedCaOptional PEM bundle that adds trust for the upstream TLS handshake. See Trust a private upstream CA.
reasonFree-text reason recorded with the decision.
cacheTtlSecondsSeconds to cache this decision. Omit or 0 to call the webhook on every request.
cacheKeyWhich request values distinguish cached decisions. See Cache decisions.

Decision types

TypeBehavior
AllowForward the request unchanged.
DenyReject with 403.
TransformApply the header mutations, then forward.
RewriteRedirect the request to scheme/host/path and optionally apply header mutations.

Cache decisions

When cacheTtlSeconds is positive, the proxy caches the decision so it does not call the webhook on every request. cacheKey controls which request values produce a distinct cache entry.

The cache is always scoped to the sandbox, hook endpoint, ARM sandbox-group resource ID, normalized hostname, and matched rule. You cannot broaden past that scope, so two rules that share a host and endpoint never share cached decisions. cacheKey narrows further within the scope:

FieldMeaning
cacheKey.fieldsAny of Scheme, Method, Path. Each named field must match for a cache hit.
cacheKey.headersHeader names — only those declared in the rule's hookRef.requestHeaders are used; other names are ignored.

Omitting a field is a promise that the decision is reusable across changes to it. For example, omit Path to serve one host-and-tenant decision for every path. When cacheKey is absent, the proxy keys on scheme, method, path, and every header declared in hookRef.requestHeaders. The proxy accepts at most eight distinct key shapes per cache scope.

Trust a private upstream CA

A Rewrite or Allow decision can return decision.trustedCa, a PEM bundle of one or more CA certificates. When the upstream request is TLS, the proxy adds these CAs to the trust store for that request only — system roots still validate, and the supplied CAs cover additional chains, such as a self-signed cluster root that a sandbox needs to reach.

{
"decision": {
"type": "Allow",
"trustedCa": "-----BEGIN CERTIFICATE-----\nMIIB...==\n-----END CERTIFICATE-----\n"
}
}

Constraints:

  • A hostname mismatch is never waived. Only chain-trust errors can be satisfied by the supplied CAs; any other TLS error still fails.
  • The PEM payload is at most 64 KiB and at most 8 certificates. Each certificate must be a CA certificate; a leaf-only bundle is rejected.
  • On a non-TLS request the proxy cannot apply trustedCa. It logs a warning and uses default trust.
  • The proxy keeps at most 8 distinct TLS-scoped configurations per sandbox source IP and routing mode, evicting the least recently used.

Apply and verify

Apply the policy file the same way as any other egress policy, then exercise the matching rule from inside the sandbox and check the audit view.

aca sandbox egress apply -l name=demo-egress --file egress.yaml
aca sandbox egress decisions -l name=demo-egress

Each decision the webhook returns is recorded like any other egress decision, with the matched rule name and the reason you supplied.