Webhook Approval Flow
How iddio routes escalation decisions to external systems. HMAC-signed webhook notifications, HTTP callback handlers, Slack integration, and the Approver interface.
The Approval Problem
Phase 1 of iddio uses terminal-based approval: when an agent’s command is escalated, the proxy prints a prompt in the operator’s terminal and waits for y/N. This works for a single operator watching a single proxy, but it doesn’t scale.
Real teams need escalation decisions routed to the systems they already use — Slack, PagerDuty, Microsoft Teams, or custom internal tools. The operator should see the escalation request in context, make a decision, and have that decision flow back to the proxy automatically.
The Approver Interface
Iddio defines an Approver interface that decouples the approval mechanism from the proxy core:
type Approver interface {
RequestApproval(ctx context.Context, req ApprovalRequest) (ApprovalResult, error)
}
type ApprovalRequest struct {
ID string
Agent string
Method string
Path string
Namespace string
Resource string
Tier int
Timestamp time.Time
}
type ApprovalResult struct {
Approved bool
Approver string
Reason string
Timestamp time.Time
}
The proxy doesn’t know or care which approver is active. It calls RequestApproval, which blocks until a decision arrives or the timeout expires (default: 60 seconds). If the timeout expires, the request is denied — fail-closed.
Webhook Notification Flow
The WebhookApprover sends an HTTP POST to a configured endpoint when an escalation occurs. The payload includes all the information needed to make a decision:
{
"id": "esc_a1b2c3d4",
"agent": "claude-code",
"method": "DELETE",
"path": "/api/v1/namespaces/payments/pods/api-7d4b8f6c9",
"namespace": "payments",
"resource": "pods",
"tier": 3,
"tier_label": "SENSITIVE",
"timestamp": "2026-01-28T10:15:30Z",
"callback_url": "https://proxy.internal:6443/api/v1/approvals/esc_a1b2c3d4",
"expires_at": "2026-01-28T10:16:30Z"
}
The notification is signed with HMAC-SHA256 using a shared secret, sent in the X-Iddio-Signature header. This prevents forged notifications from triggering bogus approval UIs.
Callback Handler
The external system makes its decision (human clicks “Approve” in Slack, or an automated policy engine evaluates it) and sends an HTTP POST back to the callback_url:
{
"id": "esc_a1b2c3d4",
"approved": true,
"approver": "alice@company.com",
"reason": "Routine pod cleanup"
}
The callback is also HMAC-signed. The proxy verifies both the signature and the escalation ID before accepting the decision.
HMAC Signature Verification
Every webhook notification and callback is signed:
func signPayload(secret []byte, body []byte) string {
mac := hmac.New(sha256.New, secret)
mac.Write(body)
return hex.EncodeToString(mac.Sum(nil))
}
func verifySignature(secret []byte, body []byte, sig string) bool {
expected := signPayload(secret, body)
return hmac.Equal([]byte(expected), []byte(sig))
}
Note the use of hmac.Equal for constant-time comparison — this prevents timing attacks where an attacker probes the signature byte-by-byte.
Slack Integration
The most common webhook target is Slack. Iddio includes a built-in Slack message formatter that converts the webhook payload into a Block Kit message with Approve/Deny buttons:
# ~/.iddio/policy.yaml
approval:
mode: webhook
webhook:
url: https://hooks.slack.com/services/T00/B00/xxx
secret: ${IDDIO_WEBHOOK_SECRET}
timeout: 60s
format: slack
When format: slack is set, the webhook payload is wrapped in Slack Block Kit JSON. The operator sees a formatted message with the agent name, command, tier classification, and two buttons. Clicking a button sends the callback back to the proxy.
Configuration
The webhook approver is configured in policy.yaml:
approval:
mode: webhook # or "terminal" for Phase 1 behavior
webhook:
url: https://your-endpoint.example.com/iddio/escalations
secret: ${IDDIO_WEBHOOK_SECRET} # env var expansion
timeout: 60s # max wait for callback
retry:
max_attempts: 3
backoff: 1s
The mode field selects the approver implementation. Both terminal and webhook can be active simultaneously — use mode: hybrid to prompt in the terminal AND send a webhook, accepting whichever response arrives first.
Timeout Behavior
If no callback arrives within the configured timeout:
- The pending request is denied (fail-closed)
- The denial is recorded in the audit log with
decision: "timeout" - The agent receives an HTTP 403 with a message explaining the timeout
This ensures that network failures or unresponsive external systems never result in an unintended allow. The proxy is fail-closed at every decision point.
Try It Yourself
Iddio is open source. Deploy a zero-trust command proxy for your AI agents in minutes.