When the firewall holds an irreversible or high-risk action for a human, it can post an interactive Slack message with Approve / Deny buttons. One click resolves it — no custom webhook handler required, and every decision is HMAC-signed for audit.
held action ──▶ Slack: "✋ Approval needed [✓ Approve] [✗ Deny]"
│ click
▼
POST /v1/slack/interactions ──▶ verify Slack signature (v0 HMAC, 5-min replay window)
──▶ record SIGNED decision (approve/deny + who + when)
──▶ drive stop-authority gate (resume / halt) [optional]
──▶ replace message: "✅ Approved by @alice"
https://<your-firewall-host>/v1/slack/interactionsbash
export AXIOM_SLACK_SIGNING_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Without it, /v1/slack/interactions returns 503 (fail-closed) — the endpoint never
trusts an unverified click.The decision logic is a small pure module (axiom_firewall/approvals.py), so you create
and post an approval from your block/hold path:
from axiom_firewall import approvals
pa = approvals.create_approval(
tenant_id="acme",
action_id="wire.transfer.4821",
summary="Wire $4,000 to vendor *Globex* (flagged: new payee)",
approver="cfo@acme.com", # a NAMED approver — fail-closed if empty
)
approvals.post_approval_request(SLACK_WEBHOOK_URL, pa) # interactive Approve/Deny message
create_approval binds the action to a named approver and signs the record; the action
stays held until the click resolves it.
Register a gate callback once at startup; it fires exactly once per decision:
from axiom_firewall import dashboard
def gate(decision, approval): # 'approve' | 'deny'
if decision == "approve":
stop_registry.resume(approval.action_id) # let the held action proceed
else:
stop_registry.halt(approval.action_id) # fail-closed: it never runs
dashboard.set_approval_gate(gate)
The gate is where the approval meets axiom_stop_authority's
fail-closed halt/resume. If you don't register one, decisions are still recorded and
signed — you just resolve the action yourself off the audit record.
v0 request signature
over the raw body; stale timestamps (> 5 min) are rejected to block replay.PendingApproval is HMAC-signed (axiom-firewall-approval-v1) with
who decided and when — tamper-evident.create_approval + the signed decision record are channel-agnostic. Point
post_approval_request at any incoming-webhook-compatible endpoint, or read the pending
approvals and drive Approve/Deny from your own dashboard/API — the same decide_approval
core backs every path.