Slack one-click approvals
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"
Setup (~5 minutes)
- Create a Slack app → Incoming Webhooks: add a webhook for the channel your approvers watch. Copy the webhook URL — that's where approval requests are posted.
- In the same app → Interactivity & Shortcuts: turn Interactivity on and set the
Request URL to:
https://<your-firewall-host>/v1/slack/interactions - Copy the app's Signing Secret (Basic Information → App Credentials) and export it
where the firewall runs:
bash export AXIOM_SLACK_SIGNING_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxWithout it,/v1/slack/interactionsreturns 503 (fail-closed) — the endpoint never trusts an unverified click.
Sending an approval request
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="[email protected]", # 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.
Wiring the decision to your action (stop-authority)
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.
Security properties
- Signed clicks only. Every callback is verified against Slack's
v0request signature over the raw body; stale timestamps (> 5 min) are rejected to block replay. - Fail-closed. No signing secret → 503. Empty approver → the approval can't be created.
- Idempotent. The first click wins; a second/conflicting click is a no-op and never re-fires the gate.
- Auditable. Each
PendingApprovalis HMAC-signed (axiom-firewall-approval-v1) with who decided and when — tamper-evident.
Not using Slack?
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.