AutomationFlowsAI & RAG › Compute Verified Campaign Statistics with Amazon Bedrock Agentcore and Slack

Compute Verified Campaign Statistics with Amazon Bedrock Agentcore and Slack

BySundar Raghavan @rsundaraws on n8n.io

This workflow receives a webhook payload of numeric campaign values, computes descriptive statistics using Amazon Bedrock AgentCore with the Code Interpreter tool, verifies the results in n8n, and posts a formatted summary to a Slack channel. Receives an HTTP POST request on a…

Webhook trigger★★★★☆ complexity9 nodes@Aws/N8N Nodes AgentcoreSlack
AI & RAG Trigger: Webhook Nodes: 9 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #17817 — we link there as the canonical source.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Calculate verified campaign statistics with Amazon Bedrock AgentCore and Slack",
  "nodes": [
    {
      "id": "37a741bd-520c-4b10-92a1-81b7daa27300",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -560,
        0
      ],
      "parameters": {
        "width": 480,
        "height": 912,
        "content": "## Calculate verified campaign statistics with Amazon Bedrock AgentCore and Slack\n\n### How it works\n\nPost a batch of numbers to the webhook and the agent writes Python, runs it in an Amazon Bedrock AgentCore code interpreter sandbox, and returns real statistics rather than predicted ones. A Code node then recomputes every reported figure in n8n and compares, so a mismatch or a run where the tool never fired is marked unverified. The verified summary is posted to Slack.\n\n### Setup steps\n\n- Install the verified community node `@aws/n8n-nodes-agentcore` from Settings, Community Nodes.\n- Add an Amazon Bedrock AgentCore API credential and select it on the agent node.\n- Add a Slack credential and set `slackChannel`, or delete that node to run with one credential.\n- Activate the workflow, then post a batch of values to the production webhook URL.\n\n### Customization\n\nKeep Add Tools switched on: with it off the tool is ignored and the agent answers from the model alone. Edit the prompt for percentiles, correlations, or a regression, since the tool is a Python environment rather than a fixed formula."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-tryit",
      "name": "Sticky Note Try It",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -560,
        960
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 352,
        "content": "### Try it\n\n```\ncurl -X POST <production-url> \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"label\":\"Q3 email\",\n       \"values\":[12,45,7,88,23,\n                 56,91,34,19,67]}'\n```\n\nMean is 44.2 and the population standard deviation is 28.944084."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-section-0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 352,
        "content": "## Receive the campaign payload\n\nThe webhook accepts a POST with a values array, and Set Channel Config holds the Slack channel and agent name."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-section-1",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        576,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 352,
        "content": "## Compute, verify, and notify\n\nThe agent runs Python in the code interpreter, a Code node recomputes every figure to verify it, then Slack gets the result."
      },
      "typeVersion": 1
    },
    {
      "id": "webhook",
      "name": "When Campaign Data Received",
      "type": "n8n-nodes-base.webhook",
      "position": [
        80,
        176
      ],
      "parameters": {
        "path": "campaign-statistics",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2
    },
    {
      "id": "config",
      "name": "Set Channel Config",
      "type": "n8n-nodes-base.set",
      "position": [
        352,
        176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-1",
              "name": "agentName",
              "type": "string",
              "value": "campaign_statistics"
            },
            {
              "id": "cfg-2",
              "name": "slackChannel",
              "type": "string",
              "value": "#analytics"
            },
            {
              "id": "cfg-3",
              "name": "label",
              "type": "string",
              "value": "={{ $json.body?.label || 'unlabelled batch' }}"
            },
            {
              "id": "cfg-4",
              "name": "values",
              "type": "array",
              "value": "={{ $json.body?.values || [] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "statistics-agent",
      "name": "Campaign Analysis Agent",
      "type": "@aws/n8n-nodes-agentcore.agentCoreHarness",
      "position": [
        656,
        176
      ],
      "parameters": {
        "tools": {
          "tool": [
            {
              "name": "code_interpreter",
              "type": "agentcore_code_interpreter"
            }
          ]
        },
        "prompt": "=Compute descriptive statistics for the batch labelled \"{{ $json.label }}\".\n\nValues: {{ JSON.stringify($json.values) }}",
        "addTools": true,
        "agentName": "={{ $json.agentName }}",
        "sessionId": "={{ 'stats-' + $execution.id }}",
        "systemPrompt": "You are a data analyst. You must compute every number by writing and running Python in the code interpreter. Never estimate, never do mental arithmetic, and never report a figure you did not compute in code. Use the population standard deviation (statistics.pstdev). Reply with a single JSON object and nothing else, using exactly these keys: label, count, mean, median, stdev, min, max. All numeric values must be plain numbers.",
        "additionalOptions": {
          "maxTokens": 4096,
          "timeoutSeconds": 300
        },
        "provisioningOptions": {
          "memoryMode": "disabled"
        }
      },
      "credentials": {
        "agentCoreApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "verify",
      "name": "Parse and Verify Data",
      "type": "n8n-nodes-base.code",
      "position": [
        928,
        176
      ],
      "parameters": {
        "jsCode": "// 1. Parse the agent's JSON defensively - it may arrive fenced or with prose.\nconst raw = String($json.response ?? '').trim();\n\nfunction extractJson(text) {\n  const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n  const candidate = fenced ? fenced[1].trim() : text;\n  try {\n    return JSON.parse(candidate);\n  } catch {}\n  const start = candidate.indexOf('{');\n  const end = candidate.lastIndexOf('}');\n  if (start !== -1 && end > start) {\n    try {\n      return JSON.parse(candidate.slice(start, end + 1));\n    } catch {}\n  }\n  return null;\n}\n\nconst stats = extractJson(raw);\nconst values = ($('Set Channel Config').item.json.values || []).map(Number).filter((n) => Number.isFinite(n));\n\nif (!stats || values.length === 0) {\n  return [{\n    json: {\n      ok: false,\n      verified: false,\n      reason: !stats\n        ? 'Could not parse a JSON object from the agent response.'\n        : 'No numeric values were supplied in the webhook body.',\n      rawResponse: raw.slice(0, 600),\n    },\n  }];\n}\n\n// 2. Recompute every figure the workflow reports, and compare. The code\n//    interpreter is exact, but any prose the model writes around the tool output\n//    is not, so nothing is labelled verified unless it was checked here.\nconst sorted = [...values].sort((a, b) => a - b);\nconst mid = Math.floor(sorted.length / 2);\n\nconst local = {\n  count: values.length,\n  mean: values.reduce((a, b) => a + b, 0) / values.length,\n  median: sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2,\n  min: sorted[0],\n  max: sorted[sorted.length - 1],\n};\n// Population standard deviation, matching statistics.pstdev in the prompt.\nlocal.stdev = Math.sqrt(\n  values.reduce((acc, v) => acc + (v - local.mean) ** 2, 0) / values.length,\n);\n\nfunction matches(reported, computed) {\n  const r = Number(reported);\n  if (!Number.isFinite(r)) return false;\n  const tolerance = Math.max(1e-6, Math.abs(computed) * 1e-6);\n  return Math.abs(r - computed) <= tolerance;\n}\n\nconst fields = ['count', 'mean', 'median', 'stdev', 'min', 'max'];\nconst fieldChecks = {};\nconst mismatched = [];\nfor (const f of fields) {\n  const ok = matches(stats[f], local[f]);\n  fieldChecks[f] = ok;\n  if (!ok) mismatched.push(f);\n}\n\n// The code interpreter must have run. A figure produced without it was predicted\n// by the model, not computed, so it does not qualify as verified.\nconst codeInterpreterUsed = Array.isArray($json.toolUses)\n  && $json.toolUses.some((t) => String(t?.name ?? '').includes('code_interpreter'));\n\nconst verified = mismatched.length === 0 && codeInterpreterUsed;\n\nlet note;\nif (verified) {\n  note = 'Every reported figure matches an independent recomputation in n8n.';\n} else if (!codeInterpreterUsed) {\n  note = 'The code interpreter did not run, so these figures came from the model rather than from executed code. Check that Add Tools is enabled.';\n} else {\n  note = `Mismatch against the local recomputation for: ${mismatched.join(', ')}. Do not trust these figures.`;\n}\n\nreturn [{\n  json: {\n    ok: true,\n    verified,\n    codeInterpreterUsed,\n    label: stats.label ?? $('Set Channel Config').item.json.label,\n    count: stats.count ?? local.count,\n    mean: Number(stats.mean),\n    median: stats.median ?? null,\n    stdev: stats.stdev ?? null,\n    min: stats.min ?? null,\n    max: stats.max ?? null,\n    check: {\n      fields: fieldChecks,\n      mismatched,\n      recomputed: local,\n      note,\n    },\n    latencyMs: $json.latencyMs ?? null,\n  },\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "post-summary",
      "name": "Send Summary to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1216,
        176
      ],
      "parameters": {
        "text": "=*Campaign statistics: {{ $json.label }}*\n{{ $json.verified ? ':white_check_mark: verified' : ':warning: NOT verified - see check.note' }}\n\n\u2022 count: {{ $json.count }}\n\u2022 mean: {{ $json.mean }}\n\u2022 median: {{ $json.median }}\n\u2022 std dev: {{ $json.stdev }}\n\u2022 min / max: {{ $json.min }} / {{ $json.max }}\n\nComputed in a microVM via the code interpreter.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Set Channel Config').item.json.slackChannel }}"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    }
  ],
  "connections": {
    "Set Channel Config": {
      "main": [
        [
          {
            "node": "Campaign Analysis Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and Verify Data": {
      "main": [
        [
          {
            "node": "Send Summary to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Campaign Analysis Agent": {
      "main": [
        [
          {
            "node": "Parse and Verify Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Campaign Data Received": {
      "main": [
        [
          {
            "node": "Set Channel Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow receives a webhook payload of numeric campaign values, computes descriptive statistics using Amazon Bedrock AgentCore with the Code Interpreter tool, verifies the results in n8n, and posts a formatted summary to a Slack channel. Receives an HTTP POST request on a…

Source: https://n8n.io/workflows/17817/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

WF-OB-2: Post-Call Handler. Uses httpRequest, slack. Webhook trigger; 48 nodes.

HTTP Request, Slack
AI & RAG

WF-SDR-TOOLS (Agent Email + Notify). Uses httpRequest, slack. Webhook trigger; 15 nodes.

HTTP Request, Slack
AI & RAG

Multi-Agent Pipeline Orchestrator. Uses httpRequest, executeCommand, slack, readWriteFile. Webhook trigger; 11 nodes.

HTTP Request, Execute Command, Slack +1
AI & RAG

PR Review - Trigger Reviewer. Uses postgres, executeCommand, slack. Webhook trigger; 10 nodes.

Postgres, Execute Command, Slack
AI & RAG

AI Support Agent - Lead Routing. Uses slack, gmail, httpRequest, googleSheets. Webhook trigger; 10 nodes.

Slack, Gmail, HTTP Request +1