n8n
n8n workflows can call a Console agent as a step, hand it long jobs and be woken when the job is done. There are four patterns, from simplest to most decoupled.
| Pattern | Use it when |
|---|---|
| A. HTTP Request node, wait for the answer | The agent answers within a minute or two and the next step needs the answer. |
| B. OpenAI node or chat model with a base URL | You want the agent behind n8n's OpenAI or AI nodes, for example in a chat trigger flow. |
| C. Long jobs: callback and the Wait node | The agent takes minutes to hours and the workflow should pause without holding a connection. |
| D. Webhook subscription trigger | One workflow reacts to every finished run of a project, whoever started it. |
This page is about n8n calling Console agents. Sending n8n's own traces to Console is a different feature: see n8n tracing.
Prepare once
- In Console, create an API token for the project with the agents service. Copy the
cpeer_…value. - In n8n, create a credential of type Header Auth: name
Authorization, valueBearer cpeer_.... Name it "Console API". - Keep your Console address handy:
https://console.example.com. Every URL below is relative tohttps://console.example.com/api/client/v1.
A. HTTP Request node, wait for the answer
Add an HTTP Request node:
| Field | Value |
|---|---|
| Method | POST |
| URL | https://console.example.com/api/client/v1/agents/support-bot/runs |
| Authentication | Generic Credential Type → Header Auth → "Console API" |
| Send Headers | Idempotency-Key = {{ $execution.id }}-ask-agent |
| Send Body | JSON |
| Options → Timeout | 120000 (more than your wait) |
| Options → Response | Include Response Headers and Status on, so you can tell 200 from 202 |
Body:
{
"input": "{{ $json.question }}",
"wait": 90,
"metadata": { "workflow": "{{ $workflow.name }}", "execution": "{{ $execution.id }}" }
}wait is in seconds, at most the server's wait window. If the agent finishes first, the node returns 200 and the run, with the answer in output_text (and output_parsed for a structured agent). If not, it returns 202 with the run in status: "running", and the run keeps going. Both are normal. Handle them with an IF node on {{ $json.statusCode }} or on {{ $json.body.status }}:
succeeded: read{{ $json.body.output_text }}.queuedorrunning(a202): go on to pattern C, or loop: Wait 10 s, then a second HTTP Request toGET …/agents/runs/{{ $json.body.id }}?wait=60, untilstatusissucceeded,failedorcanceled.
Importable skeleton (paste into the canvas; choose your credential afterwards):
{
"nodes": [
{
"name": "Ask agent",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [0, 0],
"parameters": {
"method": "POST",
"url": "https://console.example.com/api/client/v1/agents/support-bot/runs",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": { "parameters": [{ "name": "Idempotency-Key", "value": "={{ $execution.id }}-ask-agent" }] },
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={ \"input\": {{ JSON.stringify($json.question) }}, \"wait\": 90 }",
"options": { "timeout": 120000, "response": { "response": { "fullResponse": true, "neverError": true } } }
}
}
],
"connections": {}
}To use a specific version or the draft, add "version": 3 or "version": "draft". To give the agent typed values instead of free text, send variables: "variables": { "customer_id": "{{ $json.id }}" }. Console checks them before the model runs (a missing required value or a wrong type is a 400 invalid_variables), and GET /agents/{key} returns variables_schema, the JSON Schema of what the agent expects. Variables are meant for calls like this one, made by a workflow; they are not filled by chat clients.
B. OpenAI node or AI chat model
n8n's OpenAI credential accepts a custom Base URL, so Console's OpenAI-compatible endpoint works anywhere n8n uses an OpenAI model.
- Create an OpenAI credential: API Key = the
cpeer_token, Base URL =https://console.example.com/api/client/v1. - In an OpenAI Chat Model node (attached to a Basic LLM Chain), choose that credential and set the model By ID to
agent:support-bot. The same works in the OpenAI node's chat operation. - Run it. n8n sends a chat completion; Console runs the agent and answers.
Things to know:
- The agent has its own tools, knowledge and instructions. Prefer the Basic LLM Chain for this; tools you attach to n8n's AI Agent node are not forwarded to a Console agent (
tools,tool_choiceandtemperatureare ignored, listed in theX-Cognipeer-Ignored-Paramsresponse header). Give the agent its tools in Console. - Use the agent as the chat model when n8n owns the conversation (a chat trigger with memory). Console recognises a continued conversation by matching the resent history, so a Window Buffer Memory that resends earlier messages continues the same session. See session continuity.
- For a typed JSON answer, prefer pattern A with
response_format, which n8n's LLM nodes do not expose.
C. Long jobs: callback and the Wait node
When a job takes minutes, do not hold the workflow open. Create the run with a callback that points at the workflow's own resume URL, then pause on a Wait node. Console POSTs the result to that URL when the run finishes, and the workflow continues.
HTTP Request node "Start run":
POST …/agents/report-writer/runs, body:json{ "input": "Write the weekly incident report for {{ $json.week }}", "class": "deferred", "metadata": { "execution": "{{ $execution.id }}" }, "callback": { "url": "{{ $execution.resumeUrl }}", "secret": "{{ $env.CONSOLE_CALLBACK_SECRET }}" } }Leave
waitat its default0: the call returns202and a run id at once. A callback fires only for async runs, so do not set awaitlarge enough to answer inline.Wait node: Resume = On Webhook Call, HTTP Method =
POST. Optionally set a Limit Wait Time, so the execution does not wait forever if the run is cancelled.The node after the Wait receives the callback body as
{{ $json.body }}:json{ "id": "evt_65f1c0a2b9d3e4f5a6b7c8d9_succeeded", "event": "agent_run.succeeded", "createdAt": "2026-10-18T09:15:02.000Z", "runId": "run_65f1c0a2b9d3e4f5a6b7c8d9", "sessionId": "6f1c0a2b9d3e4f5a6b7c8d9e", "conversationId": "6f1c0a2b9d3e4f5a6b7c8d9e", "data": { "result": { "id": "resp_65f1c0a2b9d3e4f5a6b7c8d9", "object": "response", "status": "completed", "output_text": "…" } } }The callback is small on purpose:
dataholds theresultfor a success,errorReasonandmessagefor a failure, and nothing for a cancel. The run'sstatus,classandmetadataare not in it: the Wait node already knows which execution it belongs to, andGET …/agents/runs/{runId}has the rest.Use an IF on
{{ $json.body.event }}:agent_run.succeededgoes on;agent_run.failedandagent_run.canceledtake the error branch;agent_run.requires_actionmeans the agent waits for a person's approval.
Details that matter:
- The callback is delivered up to 5 times with backoff (2, 4, 8, 16, 32 s). If n8n restarts or the Wait node is not yet waiting when the first attempt lands, a later attempt finds it.
- The run keeps its result: if you lose the callback,
GET …/agents/runs/{runId}still has it. Store the run id in the workflow if you want a safety net. - The
callback.secretsigns the delivery (x-cognipeer-signature). The resume URL is already unguessable, so signing is optional here; verify it as in pattern D when you want defence in depth. callback.urlmust resolve to a public address. A self-hosted n8n behind a private network needs a public webhook URL (WEBHOOK_URL), a tunnel, or pattern A's polling loop instead.
D. Webhook subscription trigger
A webhook subscription sends every finished run of a project to one URL, not just runs you started from this workflow. It suits "whenever any agent run fails, notify the on-call channel".
In n8n, add a Webhook node: HTTP Method
POST, a path such asconsole-agent-runs. Under Options switch Raw Body on. Activate the workflow and copy the Production URL.Create the subscription:
bashcurl -X POST "$CONSOLE_URL/api/client/v1/webhooks" \ -H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \ -d '{ "url": "https://n8n.example.com/webhook/console-agent-runs", "events": ["agent_run.failed", "agent_run.requires_action", "trigger.failed"], "description": "n8n incident router" }'The response carries the signing secret (
whsec_…) once. Store it in n8n as the environment variableCONSOLE_WEBHOOK_SECRET.Send a test event (
POST /webhooks/{id}/test) and check that the workflow runs.Optionally verify the signature in a Code node (needs
NODE_FUNCTION_ALLOW_BUILTIN=cryptoon the n8n host):javascriptconst crypto = require('crypto'); const header = $json.headers['x-cognipeer-signature']; // t=...,v1=... const raw = Buffer.from($binary.data.data, 'base64'); // Raw Body option const parts = Object.fromEntries(header.split(',').map(p => p.trim().split('='))); const expected = crypto.createHmac('sha256', $env.CONSOLE_WEBHOOK_SECRET) .update(`${parts.t}.`).update(raw).digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ''))) { throw new Error('Bad signature'); } return $input.all();
Webhook events are thin: ids and a few fields (runId, sessionId, agentKey, status, class, metadata, triggerId). Follow up with GET …/agents/runs/{runId} for the result. Deduplicate on the webhook-id header, since a delivery can repeat.
E. Start an agent from any workflow with a webhook trigger
When the workflow only needs to hand an event to an agent, you do not need the runs API at all: add a webhook trigger to the agent (preset generic, auth bearer) and post to its URL.
- HTTP Request node:
POSTto the trigger's URL, Authentication = Generic Credential Type → Header Auth with the nameAuthorizationand the valueBearer <the trigger's secret>, Send Body as JSON. Add a headerX-Cognipeer-Event-Idset to{{ $execution.id }}, so a retried node does not start a second run. The answer is202with{ "accepted": true, "run_id": "run_…" }. - The trigger's prompt picks values from the body with Console's own tags, for example
Handle order {{payload.order.id}}(n8n expressions are not involved). - For the result, build a second workflow that starts with a Webhook node, and put its production URL (and a secret) in the trigger's
callback. The trigger's callback URL is the same for every run, so it cannot be a per-execution Wait-node resume URL (pattern C); the two halves of the job are two workflows, and the callback body carries therunIdto match them up.
In Zapier, use Webhooks by Zapier → Custom Request (POST, data as JSON, header Authorization: Bearer …) to start it and a Catch Hook trigger in a second Zap for the callback.
Error handling
| You see | Meaning | In n8n |
|---|---|---|
202 | Not an error. The window ended and the run continues, or it is queued, scheduled or deferred. | Branch on status code; use pattern C or the polling loop. |
429 agent_run_concurrency_limit | The tenant or project already has the maximum number of active runs. | Turn on Settings → Retry On Fail, 5 tries, 5000 ms apart; a stable Idempotency-Key makes retries safe. Or send "class": "deferred". |
429 spend_limit_exceeded | The tenant's spend limit is used up. | Do not retry; alert someone. |
409 agent_run_conflict | The session you continue still has an active run. | Wait and retry, or start a new session (omit session_id). |
409 idempotency_key_conflict | The same key was used for a different request. | Make the key specific to the step and the input. |
400 invalid_variables / invalid_response_format | The request failed validation; errors names the fields. | Fix the data; set On Error → Continue (using error output) and route it to a notification. |
404 | Agent not found, or not in the token's project. | Check the key and the token's project. |
Use Settings → On Error → Continue (using error output) on the HTTP Request node so a refused request becomes data you can route, instead of stopping the workflow. In the response, error.type and error.code are stable; branch on code, not on the message.

