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.

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 (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. AxonFlow does not POST automatically yet (tracked at #2419).

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 — 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 → n8n-nodes-axonflow. Restart n8n.

Manual

cd ~/.n8n/custom
npm install 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.

Important — AxonFlow does NOT automatically POST to the Wait node yet. That outbound-webhook feature is tracked at #2419 and will land in a future patch. Two paths work today.

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.

Once #2419 ships, AxonFlow will POST to the registered webhook automatically and 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.

Note — platform-side consumption is in flight. Server-side handling of Idempotency-Key on /api/v1/mcp/check-input, /api/v1/audit/tool-call, and /api/v1/hitl/queue is tracked at #2420 and lands in v8.0.2. Until then, the sent header is preserved through audit logs but does not yet deduplicate on its own. Build retry-safety into your workflow (e.g. set the Wait node to fail rather than retry on 429, or have a downstream IF node skip on a duplicate decision_id) until the platform side lands.

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 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