n8n
Connect your n8n instance and your voice agents can run the workflows you already built, during the call and after it: look an order up, create a record, send a message, then hand the finished call's report to a workflow of your choice. Works with n8n 1.x and 2.x, self-hosted or on a paid n8n Cloud plan.
How it works
A workflow that starts with a Webhook trigger is already an HTTP endpoint, so Aethex needs nothing installed in your n8n. You connect once with an n8n API key. Aethex creates a Header Auth credential in your n8n and presents its secret on every call, so no secret is ever copied between the two systems by hand. You then ask Aethex which workflows are ready, fix the ones that are not in n8n, and bind each one to a tool name, a description and the arguments the agent may pass. From then on the agent decides mid-call when a tool is worth calling, and when the call ends the full report can land in one more workflow. The whole setup is four API calls and a few clicks in n8n.
Supported versions
Every n8n 1.x and 2.x release that exposes the public API can be connected, self-hosted or on a paid n8n Cloud plan; the full flow was verified end to end on 1.123, 2.13, 2.32 and 2.37 with the same script. The experience is the same across the 2.x line. The 1.x line has one visible difference, on the credential.
| n8n version | What differs |
|---|---|
2.32 and later | Full experience. Aethex reads the key's scopes up front, so a missing scope is named on connect before anything is created, and reconnecting rotates the Aethex credential in place. |
2.0 to 2.31 | Same experience. Aethex proves the key by listing workflows and learns that a credential scope is missing only when creating the credential fails; the 422 still names the scope to grant. Reconnecting rotates in place. |
1.x | Works end to end. 1.x has no route to list or update credentials, so every reconnect creates a new Aethex (…) credential instead of rotating the existing one: select the newest in your Webhook nodes and delete the older ones. Workflows are switched on with Active rather than published. |
Connect your n8n instance
In n8n, create an API key under Settings → n8n API. It needs workflow:list and workflow:read to see your workflows, and credential:create and credential:update so Aethex can create and rotate its credential; credential:list is optional and lets Aethex find that credential again rather than create a second one. An owner or admin can grant all of them when creating the key. Send the key to POST /integrations with your instance URL in base_url (public HTTPS only) and auth_type set to api_key. Aethex proves the key against your instance before storing anything, then creates a Header Auth credential named Aethex (…), the suffix being the first characters of your workspace id, whose value is a secret Aethex generates and stores encrypted. Nothing tool-shaped goes in this call: workflows are bound after discovery. The response shows the credential Aethex will present under credential, with source set to managed. Connecting needs integrations:write.
config.webhook_base_url to that host and Aethex derives every production URL from it. If the key cannot manage credentials, pass your own webhook_secret (16 to 512 characters; openssl rand -hex 32 is a good choice) instead: Aethex records source as customer, and you create the Header Auth credential yourself in n8n with Name set to X-Aethex-Token and Value set to that secret.curl -X POST https://api.aethexai.com/api/v1/integrations \
-H "X-API-Key: $AETHEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "n8n",
"auth_type": "api_key",
"base_url": "https://n8n.your-company.com",
"access_token": "<your-n8n-api-key>",
"display_name": "Operations n8n"
}'
# 201 Created. The API key is never returned.
# Note the "id" it returns; every call below takes it.
export CONNECTION_ID=<the "id" from the response>See which workflows are ready
GET /integrations/{id}/n8n/workflows reads your instance and returns every workflow that has a Webhook trigger, judged on its published version; a workflow that only exists as a draft is listed too, flagged not_published. Each entry carries the production webhook_url Aethex derived, so you never type one, the trigger's methods and authentication, the credential it uses and whether that is the Aethex one, and a ready verdict. A workflow that is not ready lists its issues and, under fixes, the change to make in n8n for each, in the exact words below. bound_as shows the tool name a workflow already answers to, and truncated warns that the instance holds more workflows than the bounded walk covered. Nothing is written to n8n. Needs integrations:read.
| Issue | Fix |
|---|---|
not_published | Publish the workflow in n8n; only the published version answers webhooks. |
not_found | No workflow with this id is visible to the API key. |
archived | The workflow is archived. Restore it in n8n or pick another one. |
no_webhook_trigger | Add a Webhook trigger node to the workflow. |
multiple_triggers | Keep exactly one enabled Webhook trigger in the workflow. |
method_not_post | Set the Webhook node's HTTP Method to POST. |
auth_not_header | Set the Webhook node's Authentication to Header Auth. |
credential_not_aethex | Select the Aethex credential in the Webhook node. |
credential_not_in_project | Share or move the Aethex credential into the workflow's project in n8n. |
route_params | Use a fixed webhook path; route parameters like :id need values. |
curl https://api.aethexai.com/api/v1/integrations/$CONNECTION_ID/n8n/workflows \
-H "X-API-Key: $AETHEX_API_KEY"Make a workflow callable
Open the workflow in n8n. On the Webhook node set HTTP Method to POST, set Authentication to Header Auth, and pick Aethex (…) in the credential dropdown; it is already there. Keep the path fixed: a route parameter such as :id has no value Aethex could fill in. End the workflow with a Respond to Webhook node that returns JSON, and keep the whole run under 9 seconds, because the caller is waiting on the line: past that, the agent stops waiting and moves on. Then Publish, or on n8n 1.x switch the workflow to Active. Since n8n 2.0 only the published version answers production webhooks, so a saved draft changes nothing until you publish, and the same goes for every later edit. Run discovery again: the workflow now shows ready as true.
{"status": "ok", "body_empty": true} rather than a plain success. Always end a tool workflow with a Respond node that returns the fields the agent should read back to the caller; an answer with no fields means the node produced nothing.Bind workflows as tools
PUT /integrations/{id}/n8n/tools declares the whole set of tools in one call. For each workflow give the tool_name the model will call, a description that tells it when to use the tool, and parameters, a JSON Schema object describing the arguments the model may pass; n8n's Webhook node has no input schema of its own, so this is where it lives. Set writes to false on a tool that only reads, which is what lets a read-only agent see it. post_call_workflow_id names the workflow that receives the call report. Aethex re-reads every workflow from n8n before storing anything: if one is not ready, the whole request fails with 422 listing workflow_id, issues and fixes per workflow, and nothing changes. The list replaces the previous one, so [] unbinds everything. Agent grants are untouched. Needs integrations:write.
| Field | Rule |
|---|---|
workflows | At most 20 per connection, each workflow bound once. |
tool_name | 1 to 64 characters: letters, digits, _ or -, unique within the connection. end_call, voicemail_detected, transfer_to_number, send_whatsapp_message and n8n_send_call_report are reserved. |
description | Plain text, 1 to 300 characters. The model reads it to decide when to call the tool, so say what the tool does and when it applies. |
parameters | A JSON Schema with type set to object, at most 8 KiB. Every name in required must be declared in properties. Defaults to an object with no properties. |
writes | Defaults to true. Set false on a tool that only reads, so agents with a read grant can see it. |
curl -X PUT https://api.aethexai.com/api/v1/integrations/$CONNECTION_ID/n8n/tools \
-H "X-API-Key: $AETHEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workflows": [
{
"workflow_id": "nP72CdKcpWs1H6Ei",
"tool_name": "lookup_order",
"description": "Return the status and delivery date of an order from its order number.",
"parameters": {
"type": "object",
"properties": {
"order_id": { "type": "string", "description": "The order number the caller gives" }
},
"required": ["order_id"]
},
"writes": false
}
],
"post_call_workflow_id": "X0pMPZCPehNtOtX7"
}'Grant it to agents
Binding a tool does not hand it to anyone. Grant the connection per agent with PUT /integrations/{id}/agents, stating the complete set each time: the agents you send are the ones with access, and an empty list means nobody. The level decides what an agent sees. read offers only the tools bound with writes set to false; write offers every tool and turns on the post-call report for that agent's calls. An agent with no grant never sees an n8n tool, whatever is bound. Grants are read when a call starts, so a change applies to the next call.
curl -X PUT https://api.aethexai.com/api/v1/integrations/$CONNECTION_ID/agents \
-H "X-API-Key: $AETHEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agents": [
{ "agent_id": "<agent-uuid>", "access": "write" }
]
}'What your workflow receives
When the agent calls a tool, Aethex sends a POST to the workflow's production URL with the header X-Aethex-Token set to the credential's secret. The body carries tool, the model's arguments under input, and agent_id, conversation_id and call_id so the workflow can correlate the call with the report that follows. Read an argument in n8n as $json.body.input.order_id for an argument named order_id. The key is input rather than arguments on purpose: n8n's expression sandbox refuses the identifier arguments, so a workflow could never read the values under that name. Whatever JSON object the Respond node returns goes back to the model as is, a JSON array comes back under result, and an empty body becomes {"status": "ok", "body_empty": true}, so the agent can tell a workflow that returned nothing from one that answered. The agent gets its answer within 9 seconds, the deadline every tool runs under. If the workflow is still running then, a tool bound with writes: false is abandoned and the agent is told it got no answer; one bound with writes: true is left to finish, and the agent is told the outcome is unknown and must not repeat it. The request itself stops at 10 seconds, and a response is capped at 32 KiB. After five failures in a row Aethex pauses calls to your instance for 30 seconds and tells the agent the tool is unavailable, so one broken workflow does not stall every call.
N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT. One more n8n rule when writing the Respond body: inside an expression, two closing braces in a row end it even in a nested object literal, so put a space between them.POST https://n8n.your-company.com/webhook/order-lookup
X-Aethex-Token: <the credential's secret>
Content-Type: application/json
{
"tool": "lookup_order",
"input": { "order_id": "4821" },
"agent_id": "2f6c6a1e-9d8b-4c1a-9f3e-0b7d5e4a1c22",
"conversation_id": "7d1a3f0c-52b4-4e6e-8a9f-3c2d1b0e9f88",
"call_id": "c0ffee00-1111-4222-8333-444455556666"
}Receive the call report
When a call ends, Aethex posts the same report your call.ended webhook receives to the workflow you named in post_call_workflow_id, with one extra field, event, set to call.ended: call, conversation and agent ids, direction and numbers, duration and cost, timestamps and the transcript. It uses the same header and the same Webhook setup as a mid-call tool, with a 30-second budget instead of 10 since nobody is waiting on the line, and it fires only for agents with a write grant. The report is sent at most once per call: its event_id is derived from the call id, so a retry never yields a second, different report, and a push that may already have left is not repeated. A workflow that fails on the report does not fail the call, and the outcome is not shown on the call itself, so confirm delivery in the workflow's Executions in n8n. See Webhooks for the field-by-field payload.
{
"event": "call.ended",
"event_id": "6f1d2c3b-4a5e-5f60-9b7c-8d9e0f1a2b3c",
"timestamp": 1756900000,
"call_id": "c0ffee00-1111-4222-8333-444455556666",
"conversation_id": "7d1a3f0c-52b4-4e6e-8a9f-3c2d1b0e9f88",
"agent_id": "2f6c6a1e-9d8b-4c1a-9f3e-0b7d5e4a1c22",
"status": "completed",
"direction": "inbound",
"from_number": "+2348012345678",
"to_number": "+2341234567890",
"duration_seconds": 184,
"cost_cents": 12,
"started_at": "2026-09-03T12:40:11Z",
"ended_at": "2026-09-03T12:43:15Z",
"transcript": [
{ "role": "user", "content": "Hi, I want to check on order 4821." },
{ "role": "assistant", "content": "Order 4821 shipped with DHL and arrives on the 5th." }
],
"transcript_text": "user: Hi, I want to check on order 4821.\nassistant: Order 4821 shipped ...",
"transcript_truncated": false
}Security
Your n8n instance must be reachable over public HTTPS; private, loopback and internal-only hosts are refused, and the hostname is resolved and pinned immediately before each request. Redirects are never followed; API reads time out at 15 seconds and are capped at 1 MiB, webhook calls at 10 seconds and 32 KiB. During a call the agent still answers within 9 seconds whatever n8n does, so size mid-call workflows to that, not to these limits. The API key and the webhook secret are encrypted at rest and never returned by any endpoint, and your workflows' own credentials never leave n8n. Reconnecting rotates the secret in place, so every Webhook node already using the Aethex credential keeps working with the new value on n8n 2.x; on 1.x a reconnect creates a new credential, see Supported versions. The header check is the only thing protecting a workflow from the outside: a trigger left on Authentication: None answers anyone who knows its URL, which is why discovery refuses it.
X-Aethex-Token value. Treat that access as access to your agents' tools: restrict who can view executions, turn off saving production executions in the settings of the bound workflows if you do not need the data, and reconnect to rotate the secret the moment you suspect it leaked.When a call fails
401 and 403 apply to every call here and fire before any handler runs. Past that, connecting returns 404, 409, 422, 429 or 503; discovery and binding return 404, 409, 422, 429 or 503 and carry n8n's own message in the detail when your instance refused something; setting agents returns 404 or 409.
Every call on this page, before any handler runs. No X-API-Key header, or a key that is invalid, revoked or expired.
Send a live key. Missing API key means the header never arrived; Invalid or expired API key means it did not match.
Every call, before any handler runs. The key is valid but lacks the scope this call needs: integrations:write to connect, bind or set agents, integrations:read to discover or read a connection back.
The detail names the missing scope. Scopes are fixed at key creation, so mint a key that carries it rather than widening this one.
Connecting, discovery, binding and setting agents. The connection id is not yours or is not an n8n connection, or an agent_id is not one of your agents.
Check the id from the connect response, and that every agent_id is an agent on this tenant. A foreign id is deliberately indistinguishable from a missing one.
Discovery, binding and setting agents. The connection exists but is no longer the active one, because it was revoked or replaced by a reconnect, or a concurrent request changed the same grants.
Read the connection back: if it is no longer active, work from the id the latest connect returned. Otherwise re-send; the bodies are declarative, so a retry converges.
Connecting: n8n rejected the API key, the key lacks an n8n scope the detail names, base_url is missing or not public HTTPS, or config carries a field other than webhook_base_url. Binding: a tool_name is reserved or malformed, parameters is not an object schema, or a workflow is not ready, in which case the detail lists workflows with issues and fixes. Discovery and binding also return 422 when n8n answers an error that will not clear on its own.
Read the detail: it names the field, the n8n scope to grant, or the workflow and the fix to make in n8n. A bind 422 stored nothing, so re-send the whole list once the fixes are in.
Connecting: more than 10 attempts in 60 seconds from one tenant. Discovery and binding: more than 35 requests in 60 seconds, since each one reads your n8n instance.
Wait for the window in the Retry-After header. Discovery is a setup step, not something to poll.
Connecting, discovery and binding. Your n8n instance was unreachable or timed out, the encryption key is missing on this deployment, or the rate limiter is offline.
Retry, and read the detail for which of the three you hit. An unreachable instance clears on its own; a missing key or offline limiter is ours and keeps failing until fixed.
Notes and limits
A few things worth knowing before you plan around them.
| Limit | What it means |
|---|---|
| Reconnecting starts over | A workspace holds one active n8n connection. Reconnecting rotates the credential in place on n8n 2.x, so your Webhook nodes keep working, but it is a new connection: agent grants are not carried over and the bound tools are gone until you declare them again with PUT /integrations/{id}/n8n/tools. |
| Published version only | Aethex reads and calls the published version of a workflow. Editing a draft changes nothing until you publish, and unpublishing makes the tool fail until you publish again. |
| The credential lives in one project | Aethex creates its credential in the personal project of the user who owns the API key. A workflow in another project cannot select it until you share or move the credential in n8n; discovery reports that as credential_not_in_project. |
| Discovery is bounded | Discovery walks up to 1,000 workflows and sets truncated when the instance holds more. Binding by id still works for any workflow the key can read. |
| Grants apply at call start | Removing a grant takes effect on the next call, not one already in progress. Disconnecting bites immediately. |
