Skip to content

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.

PatternUse it when
A. HTTP Request node, wait for the answerThe agent answers within a minute or two and the next step needs the answer.
B. OpenAI node or chat model with a base URLYou want the agent behind n8n's OpenAI or AI nodes, for example in a chat trigger flow.
C. Long jobs: callback and the Wait nodeThe agent takes minutes to hours and the workflow should pause without holding a connection.
D. Webhook subscription triggerOne 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 ​

  1. In Console, create an API token for the project with the agents service. Copy the cpeer_… value.
  2. In n8n, create a credential of type Header Auth: name Authorization, value Bearer cpeer_.... Name it "Console API".
  3. Keep your Console address handy: https://console.example.com. Every URL below is relative to https://console.example.com/api/client/v1.

A. HTTP Request node, wait for the answer ​

Add an HTTP Request node:

FieldValue
MethodPOST
URLhttps://console.example.com/api/client/v1/agents/support-bot/runs
AuthenticationGeneric Credential Type → Header Auth → "Console API"
Send HeadersIdempotency-Key = {{ $execution.id }}-ask-agent
Send BodyJSON
Options → Timeout120000 (more than your wait)
Options → ResponseInclude Response Headers and Status on, so you can tell 200 from 202

Body:

json
{
  "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 }}.
  • queued or running (a 202): go on to pattern C, or loop: Wait 10 s, then a second HTTP Request to GET …/agents/runs/{{ $json.body.id }}?wait=60, until status is succeeded, failed or canceled.

Importable skeleton (paste into the canvas; choose your credential afterwards):

json
{
  "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.

  1. Create an OpenAI credential: API Key = the cpeer_ token, Base URL = https://console.example.com/api/client/v1.
  2. 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.
  3. 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_choice and temperature are ignored, listed in the X-Cognipeer-Ignored-Params response 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.

  1. 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 wait at its default 0: the call returns 202 and a run id at once. A callback fires only for async runs, so do not set a wait large enough to answer inline.

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

  3. 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: data holds the result for a success, errorReason and message for a failure, and nothing for a cancel. The run's status, class and metadata are not in it: the Wait node already knows which execution it belongs to, and GET …/agents/runs/{runId} has the rest.

    Use an IF on {{ $json.body.event }}: agent_run.succeeded goes on; agent_run.failed and agent_run.canceled take the error branch; agent_run.requires_action means 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.secret signs 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.url must 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".

  1. In n8n, add a Webhook node: HTTP Method POST, a path such as console-agent-runs. Under Options switch Raw Body on. Activate the workflow and copy the Production URL.

  2. Create the subscription:

    bash
    curl -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 variable CONSOLE_WEBHOOK_SECRET.

  3. Send a test event (POST /webhooks/{id}/test) and check that the workflow runs.

  4. Optionally verify the signature in a Code node (needs NODE_FUNCTION_ALLOW_BUILTIN=crypto on the n8n host):

    javascript
    const 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.

  1. HTTP Request node: POST to the trigger's URL, Authentication = Generic Credential Type → Header Auth with the name Authorization and the value Bearer <the trigger's secret>, Send Body as JSON. Add a header X-Cognipeer-Event-Id set to {{ $execution.id }}, so a retried node does not start a second run. The answer is 202 with { "accepted": true, "run_id": "run_…" }.
  2. 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).
  3. 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 the runId to 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 seeMeaningIn n8n
202Not 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_limitThe 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_exceededThe tenant's spend limit is used up.Do not retry; alert someone.
409 agent_run_conflictThe session you continue still has an active run.Wait and retry, or start a new session (omit session_id).
409 idempotency_key_conflictThe same key was used for a different request.Make the key specific to the step and the input.
400 invalid_variables / invalid_response_formatThe request failed validation; errors names the fields.Fix the data; set On Error → Continue (using error output) and route it to a notification.
404Agent 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.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub