Skip to main content

n8n + AxonFlow Integration

AxonFlow gives n8n workflows a way to call the AxonFlow API for policy checks, decision recording, and human-in-the-loop approvals. There are two ways to wire it up.

Governance coverage
  • Interception point: an explicit AxonFlow node (the community node) or stock HTTP nodes that you place in the workflow — a WCP-style step gate, not an automatic interceptor.
  • Governs: the specific step(s) where you wire it in — the default checkPolicy operation calls POST /api/v1/mcp/check-input before the guarded action, recordDecision / auditLog write the audit (POST /api/v1/audit/tool-call), and waitForApproval pauses the workflow for a HITL decision (POST /api/v1/hitl/queue).
  • Does not govern: steps where the node is not placed. Coverage is opt-in per workflow — an ungated node runs without any policy check.

See Governance Architecture & Coverage for how this fits the five runtime modes.

PathWhen to use
Tier 1 — Stock HTTP-node recipeWorks on any n8n install, including n8n Cloud. Four HTTP nodes per governed action. Useful for prototypes or as a fallback when community nodes are not available.
Tier 2 — Community node (@axonflow/n8n-nodes-axonflow)Self-hosted n8n. One node, four operations, idempotency by default, paired with the built-in Wait node for HITL resume.

Tier 3 — verified node on n8n Cloud. Submission to n8n's verified registry is tracked separately and is not gated by this page.


Tier 1 — Stock HTTP-node recipe (any n8n install)

This recipe uses only built-in n8n nodes. It is the fastest path to "n8n is calling AxonFlow" — useful for prototyping or n8n Cloud where community nodes are not currently allowed.

Setup once

In Credentials → New → Header Auth create an AxonFlow Basic credential:

FieldValue
NameAuthorization
ValueBasic <base64(clientId:userToken)>

Why Header Auth and not Bearer Auth? n8n's built-in Bearer Auth class silently drops the header on some versions (n8n#15261). Header Auth attaches the Authorization line explicitly and is unaffected.

Wire four HTTP nodes per governed action

[HTTP] Trigger → [HTTP] AxonFlow Check Policy → [IF] allowed?
├── true → [HTTP] downstream action → [HTTP] AxonFlow Record Decision
└── false → [Wait] for webhook → [HTTP] downstream action → [HTTP] AxonFlow Record Decision
(error branch) → [HTTP] AxonFlow Audit Log
NodeMethod + URLBody (JSON)
Check PolicyPOST {endpoint}/api/v1/mcp/check-input{ "client_id": "{{ $env.AXONFLOW_CLIENT_ID }}", "user_token": "{{ $env.AXONFLOW_USER_TOKEN }}", "tenant_id": "{{ $env.AXONFLOW_CLIENT_ID }}", "connector_type": "n8n", "statement": "{{ $json.action_description }}", "operation": "execute" }
Record DecisionPOST {endpoint}/api/v1/audit/tool-call{ "tool_name": "{{ $json.tool_name }}", "tool_type": "n8n_decision", "workflow_id": "{{ $workflow.id }}", "input": {{ JSON.stringify($('Trigger').item.json) }}, "output": {{ JSON.stringify($json) }}, "success": true }
Audit Log (error branch)same URL as Record Decisionsame body shape, "success": false, "error_message": "{{ $json.error }}"
Wait for ApprovalPOST {endpoint}/api/v1/hitl/queue{ "client_id": "{{ $env.AXONFLOW_CLIENT_ID }}", "original_query": "{{ $json.description }}", "request_type": "workflow_step", "triggered_policy_id": "n8n-manual", "triggered_policy_name": "n8n manual approval", "trigger_reason": "Workflow gate", "severity": "medium", "expires_in_seconds": 86400 } — followed by a built-in Wait node configured for "On Webhook Call" mode. To actually resume on approval, store the Wait node's webhook URL and either trigger it from a polling sidecar (see HITL pattern) or have a reviewer POST to it manually after deciding in the portal. As of v8.1.0, AxonFlow can POST to this URL automatically via the notify_url field — see the v8.1.0 release notes for details.

Always set an Idempotency-Key header on every HTTP node so n8n's Retry on Fail does not double-record. Template: {{ $execution.id }}-{{ $itemIndex }}-{{ $node.name }}.


Tier 2 — @axonflow/n8n-nodes-axonflow community node

A single community node with four operations, idempotency-key handling, and a credential type that uses Header Auth (not the buggy Bearer Auth class).

Install

n8n GUI (self-hosted)

Settings → Community Nodes → Install → @axonflow/n8n-nodes-axonflow. Restart n8n.

Manual

cd ~/.n8n/custom
npm install @axonflow/n8n-nodes-axonflow
# restart n8n

n8n Cloud does not currently allow unverified community nodes. Use the Tier 1 recipe until this package is verified.

Configure the credential

Credentials → New → AxonFlow API.

FieldDescription
EndpointBase URL of your AxonFlow Agent. SaaS: https://try.getaxonflow.com. Self-hosted: typically port 8080.
Client IDYour tenant identifier.
User TokenSent as the password half of HTTP Basic auth. Stored encrypted in n8n.

The four operations

OperationEndpointUse it…
Check PolicyPOST /api/v1/mcp/check-inputBefore a sensitive action — branch on {allowed, block_reason?}.
Record DecisionPOST /api/v1/audit/tool-callAfter a successful action — capture inputs, outputs, success.
Audit LogPOST /api/v1/audit/tool-callFrom error branches — same endpoint with success: false.
Wait for ApprovalPOST /api/v1/hitl/queue + Wait nodeWhen you need a human signoff — pause until reviewer responds.

HITL pattern: Wait node + webhook resume

The Wait for Approval operation creates the AxonFlow approval entry and immediately returns the approval_id. To actually pause-and-resume on the reviewer decision, pair it with a built-in Wait node:

AxonFlow Wait for Approval → Wait (mode: On Webhook Call) → downstream action

Configure the Wait node:

  1. Resume: On Webhook Call
  2. Webhook Method: POST
  3. Webhook URL is auto-generated by n8n. Store this URL — you'll either trigger it from a polling sidecar or hand it to a reviewer for manual resume.

AxonFlow v8.1.0+ can POST to the Wait node automatically via the notify_url field on /api/v1/hitl/queue. When notify_url is set, the platform fires a signed webhook to that URL on approval/rejection — no polling sidecar needed. For self-hosted deployments still on v8.0.x, two manual paths work.

Run a small process that polls AxonFlow for the approval status and POSTs to the Wait node when the reviewer decides. Minimal reference (any language; bash + curl shown):

#!/usr/bin/env bash
# resume-sidecar.sh APPROVAL_ID WEBHOOK_URL
set -euo pipefail
APPROVAL_ID="$1"; WEBHOOK_URL="$2"
POLL_INTERVAL="${POLL_INTERVAL:-10}"
# Cap the total wait at the AxonFlow approval default (24h) plus a small buffer
# so a never-decided approval can't leak the sidecar process forever.
MAX_POLLS="${MAX_POLLS:-9000}" # 9000 × 10s = 25h
for i in $(seq 1 "$MAX_POLLS"); do
RESP="$(curl -sS --fail-with-body \
-u "$AXONFLOW_CLIENT_ID:$AXONFLOW_USER_TOKEN" \
"$AXONFLOW_ENDPOINT/api/v1/hitl/queue/$APPROVAL_ID")" || {
echo "warn: HITL fetch failed (iter=$i) — will retry" >&2
sleep "$POLL_INTERVAL"; continue
}
STATUS="$(echo "$RESP" | jq -r '.data.status')"
case "$STATUS" in
approved|rejected|overridden|expired|cancelled)
curl -sS --fail-with-body -X POST -H 'Content-Type: application/json' \
-d "$(echo "$RESP" | jq '.data')" "$WEBHOOK_URL"
exit 0
;;
esac
sleep "$POLL_INTERVAL"
done
echo "error: HITL approval $APPROVAL_ID timed out after $MAX_POLLS polls" >&2
exit 1

Invoke once from the n8n workflow itself (an Execute Command or HTTP node), or run it as a long-lived process keyed by approval_id. The Wait node receives the reviewer decision payload as $json in the next node.

The terminal-states case (approved | rejected | overridden | expired | cancelled) covers every status the AxonFlow HITL service emits — anything else is a transient pending and keeps polling. If the n8n Wait node has its own wait-timeout and you'd rather not leak the sidecar, kill the process when the workflow expires.

Resume path B — manual portal resume

The reviewer copies the Wait node's webhook URL from the n8n workflow editor and POSTs to it from the AxonFlow portal's approval-detail screen after approving or rejecting. Works without a sidecar; trades automation for simplicity. Recommended for small operations teams or during integration smoke-tests.

On v8.1.0+, pass the Wait node's webhook URL as notify_url in the HITL queue request and AxonFlow will POST to it automatically on approval — the polling sidecar can be retired. The Wait-node side of the workflow is unchanged.

Idempotency by default

Every operation sends an Idempotency-Key header. The default template is:

{{ $execution.id }}-{{ $itemIndex }}-{{ $node.name }}

This pins the request to a unique tuple — execution × item × node — so a future server-side deduplicator can reject re-sends from n8n's Retry on Fail. The header is structurally correct today.

Platform-side Idempotency-Key consumption is live. As of platform v8.1.0, the three endpoints (/api/v1/mcp/check-input, /api/v1/audit/tool-call, /api/v1/hitl/queue) consume the Idempotency-Key header and deduplicate retries within 24 hours. Duplicate requests return the cached response with an Idempotent-Replayed: true header. See the v8.1.0 release notes for details.

Override the key only if you have a domain-specific deduplication identifier (e.g. an upstream request ID that you want to dedupe across executions).

Bearer Auth gotcha

n8n's built-in Bearer Auth class silently drops the Authorization header in some versions (n8n#15261). Credentials look configured, the test passes, every actual call returns 401.

The AxonFlowApi credential bundled with this package uses the Header Auth pattern instead — Authorization is built inline from clientId + userToken and attached to every request. You do not need to worry about the bug; it is documented here so you do not get caught by it when building a Tier 1 recipe.


Example workflow

The @axonflow/n8n-nodes-axonflow package ships an importable workflow at examples/governed-loan-workflow.json that wires up:

  1. HTTP trigger receives a loan request,
  2. Check Policy evaluates the proposed action,
  3. IF branch — if allowed=true, proceeds to issue the loan; if allowed=false, routes through Wait for Approval and a Wait node,
  4. After the loan-issuance HTTP node succeeds, Record Decision captures the outcome,
  5. Error branch routes to Audit Log with success: false.

Import it via Workflows → Import from File and point the Issue Loan (HTTP) node at your downstream service.


Reference

Implementation Checklist

Before you put this integration in front of real users, connect it back to the core runtime docs: