logo

Are you need IT Support Engineer? Free Consultant

SAP Cloud Integration (CI/CPI) – Self-Healing iFlo…

  • By sujay
  • 12/06/2026
  • 25 Views

A concept for AI powered integration recovery using n8n workflows
and a Large Language Model (LLM) alongside SAP Cloud Integration (CPI)

This blog presents a concept for making SAP Cloud Integration (CPI) iFlows self-healing using n8n and a Large Language Model(LLM). The idea is straightforward: instead of waiting for someone to manually diagnose a failure, update a credential, and replay a message, what if the system could detect the failure, understand what went wrong, and fix the root cause automatically with human approval?

What is described here is a proof of concept built to demonstrate this idea. It focuses on one specific and very common failure scenario: an iFlow fails because a credential expired. The concept can be extended to other failure types based on your specific requirements. The goal of this blog is to share the idea, show how it can be implemented, and spark discussion about what AI-assisted integration operations could look like in practice.

Note:  This solution was built and tested on SAP Integration Suite running on SAP BTP. It uses n8n community edition (free, self-hosted) and OpenAI GPT-4o-mini for AI classification. It is a concept implementation, not a production-ready product.

The framework runs as four n8n workflows that sit alongside your existing Cloud Integration (CPI) tenant. No existing iFlow logic is changed. Two small additive steps are added to the iFlow error handler. Everything else runs in n8n.

Pic_N8N.png

What the LLM Adds

The LLM does something a regex classifier cannot: it understands context. It reads the full error text, considers the iFlow name, the credential involved, and writes a plain English explanation for the operator, suggests a root cause, and detects recurring patterns.

Example LLM output for a 401 error:

{
  "error_type":        "CREDENTIAL_EXPIRED",
  "confidence":        94,
  "risk_level":        "LOW",
  "plain_english":     "The bank API rejected the credentials because the password expired.",
  "likely_cause":      "The bank rotates credentials on a monthly cycle.",
  "recommended_action":"Update the credential and set a monthly rotation reminder.",
  "pattern_detected":  "4 credential failures in 30 days, all on the last Monday of the month."
}

Tip:  The pattern detection is the most valuable output over time. After 3 to 4 incidents, the LLM starts detecting monthly rotation cycles and recurring failure patterns that no monitoring dashboard would surface.

Requirement

Details

SAP Cloud Integration (CPI) tenant

Any edition of SAP Integration Suite on BTP

n8n

Free community edition, self-hosted via Docker

Docker Desktop

Free download from docker.com

OpenAI API key

From platform.openai.com (or use Anthropic/Ollama instead)

Microsoft Teams

A channel with an Incoming Webhook connector configured

CPI OAuth2 service key

Create a service key on the Process Integration Runtime instance in BTP cockpit

ngrok (free)

To expose your local n8n to the internet so CPI can call it

Step 1: Install n8n Using Docker

Open Terminal on your Mac and run:

docker run -d \
  --name n8n \
  --restart unless-stopped \
  -p 5678:5678 \
  -v "$(pwd)/data:/home/node/.n8n" \
  --env-file n8n_configuration_file.env \
  n8nio/n8n

Then open http://localhost:5678 in your browser and complete the setup.

Step 2: Create the Configuration File

Create a file called n8n_configuration_file.env with the following content:

N8N_ENCRYPTION_KEY=your_32_char_random_key
N8N_BLOCK_ENV_ACCESS_IN_NODE=false
N8N_BASIC_AUTH_ACTIVE=false
 
CPI_BASE_URL=https://your-tenant.it-accd003.cfapps.eu12.hana.ondemand.com
TEAMS_WEBHOOK_URL=https://yourcompany.webhook.office.com/webhookb2/xxxxx
N8N_BASE_URL=http://localhost:5678
 
N8N_CRED_MOCK_BANK_BASIC_AUTH_USER=demo_user
N8N_CRED_MOCK_BANK_BASIC_AUTH_PASSWORD=demo_pass_correct

Warning:  N8N_BLOCK_ENV_ACCESS_IN_NODE=false is required. Without it you will see [ERROR: access to env vars denied] in every node that reads configuration values.

Step 3: Add the CPI OAuth2 Credential in n8n

In n8n, go to Home screen and click Credentials

1. Click Add Credential and search for OAuth2 API

2. Fill in the values from your CPI service key:

Field

Value

Credential Name

CPI OAuth2

Grant Type

Client Credentials

Access Token URL

Your tokenurl value from the service key

Client ID

Your clientid value from the service key

Client Secret

Your clientsecret value from the service key

3. Click Save

Step 4: Add the LLM Credential

  1. In n8n Credentials, click Add Credential and search for OpenAI
  2. Paste your OpenAI API key and save it as OpenAI API

Note:  You can also use Anthropic Claude or Ollama (free, on-premise). Ollama runs entirely on your own server so no data leaves your network. Swap the LLM node type after importing the workflow.

Step 5: Set Up the iFlow Registry

The registry controls which iFlows are opted in to self-healing. It is a JavaScript array embedded directly inside the Registry Gate node of the Detector workflow. An iFlow not in this list will never be auto-healed regardless of what error it produces.

After importing the Detector workflow in Step 6, open it and find the Registry Gate node. Edit the REGISTRY array to include your iFlow:

const REGISTRY = [
  {
    "iflow_name":      "YOUR_IFLOW_NAME",
    "enabled":         true,
    "heal_credential": true,
    "max_attempts":    3,
    "credential_name": "your_cpi_credential_name"
  }
];

Note:  The iflow_name must match exactly what the Groovy script sends as iflow_name (which comes from ${camelId} in the error handler). The credential_name must match exactly the name shown in CPI Security Material.

Step 6: Build Three n8n Workflows

Import the workflow JSON files in this order:

Order

File

Workflow Name

1

workflow_mock_bank.json

Cloud Integration Self Healer – Mock Bank API

2

workflow_detector.json

Cloud Integration Self Healer – Detector and Classifier

3

workflow_credential_healer.json

Cloud Integration Self Healer – Credential Healer

 

1. Cloud Integration Self Healer – Mock Bank API

This workflow is used to mock the Bank API

Adarshrao_0-1781202046131.Png

 

Mock Bank Webhook:

Receives the request sent from the Cloud Integration Iflow

Adarshrao_1-1781183854512.Png

Check Credentials:

To check if the credentials sent is correct or wrong from Cloud Integration iFlow

Adarshrao_3-1781184362451.Png

 

// ── MOCK BANK — Basic Auth reader ──────────────────────────
// Reads standard Authorization: Basic  header.

const h       = $input.item.json.headers || {};
const authRaw = h['authorization'] || h['Authorization'] || '';

let user="";
let pass="";

if (authRaw.toLowerCase().startsWith('basic ')) {
  const b64     = authRaw.substring(6).trim();
  const decoded = Buffer.from(b64, 'base64').toString('utf8');
  const colon   = decoded.indexOf(':');
  if (colon > -1) {
    user = decoded.substring(0, colon);
    pass = decoded.substring(colon + 1);
  }
}

const valid = user === 'demo_user' && pass === 'demo_pass_correct';

console.log(`[MockBank] user=${user} valid=${valid}`);

if (valid) {
  return [{ json: {
    _is401: false,
    _body: '{"status":"accepted","transactionId":"TXN-1234567890","message":"Payment instruction received"}'
  }}];
} else {
  return [{ json: {
    _is401: true,
    _body: '{"error":"Unauthorized","message":"Invalid credentials — check username and password","code":"AUTH_FAILED","receivedUser":"' + user + '"}'
  }}];
}

Respond to Caller:

Adarshrao_4-1781184437633.Png

2. Cloud Integration Self Healer – Detector and Classifier

Whenever there is an error in Cloud Integration iFlow, this workflow can be triggered from Exception SubProcess or common Exception Handler iFlow.

N8N2.Png

Cloud Integration Failure Webhook:

Adarshrao_0-1781185375546.Png

Registry Gate:

The Registry Gate is a Code node that acts as the opt-in list. It checks whether the failed iFlow is registered for self-healing and attaches its configuration to the payload for all downstream nodes to use.

const REGISTRY = [
  {
    "iflow_name":      "FIN_AP_VendorInvoice",
    "enabled":         true,
    "heal_credential": true,
    "max_attempts":    3,
    "credential_name": "mock_bank_basic_auth"
  },
  {
    "iflow_name":      "LOG_Delivery_3PL",
    "enabled":         true,
    "heal_credential": false,
    "max_attempts":    1,
    "credential_name": null
  },
  {
    "iflow_name":      "HR_Replication_Daily",
    "enabled":         false,
    "heal_credential": false,
    "max_attempts":    0,
    "credential_name": null
  }
];

const payload   = $input.item.json.body || $input.item.json;
const iflowName = payload.iflow_name;

const entry = REGISTRY.find(
  function(r) { return r.iflow_name === iflowName && r.enabled === true; }
);

if (!entry) {
  return [{ json: Object.assign({}, payload, {
    _registered: false,
    _escalate_only: true,
    _registry_entry: null
  }) }];
}

return [{ json: Object.assign({}, payload, {
  _registered: true,
  _escalate_only: false,
  _registry_entry: entry
}) }];

Production note: “In this PoC the registry is hardcoded in the node. For production, consider moving it to Google Sheets or a configuration endpoint so the operations team can manage it without modifying the workflow. Similarly, the correct credential password is stored as an environment variable. For production, replace the Fetch New Credential node with a call to Azure Key Vault or your organization's secrets management solution. That is a single node change. Everything else stays the same.

Is Registered?:

Check if the iFlow is registered for self-healing.

Adarshrao_1-1781186239357.Png

Unregistered – Format Alert:

// Unregistered iFlow - send a minimal Teams alert to the
// default escalation channel (not the iFlow owner, since
// the registry has no owner info for this iFlow)
const item = $input.item.json;
return [{
  json: {
    alert_type: 'UNREGISTERED_IFLOW',
    iflow_name: item.iflow_name,
    message_guid: item.message_guid,
    error_text: item.error_text,
    message: `iFlow '${item.iflow_name}' failed but is not in the self-healing registry. Add it to iflow_registry.json to enable auto-healing.`
  }
}];

Dedup Guard:

It prevents the same failure from being processed twice. When a failure arrives, it checks whether the message GUID has already been seen by storing processed GUIDs in n8n workflow static data. If the same GUID arrives again within 24 hours, for example from a retry storm in CPI, it is silently dropped so the healing process does not trigger twice for the same incident.

// ── DEDUPLICATION GUARD ────────────────────────────────────
// Prevent processing the same MessageGuid twice.
// Uses n8n workflow static data as a simple in-memory store.
// For production, replace with a Postgres insert + SELECT.

const staticData = $getWorkflowStaticData('global');
if (!staticData.processedGuids) staticData.processedGuids = {};

const guid = $input.item.json.message_guid;
const now = Date.now();

if (staticData.processedGuidshttps://community.sap.com/t5/technology-blog-posts-by-sap/sap-cloud-integration-ci-cpi-self-healing-iflows-with-n8n-and-ai/ba-p/14416711) {
  // Already processed - stop here
  return [];
}

// Record as processing (TTL: 24 hours to prevent unbounded growth)
staticData.processedGuidshttps://community.sap.com/t5/technology-blog-posts-by-sap/sap-cloud-integration-ci-cpi-self-healing-iflows-with-n8n-and-ai/ba-p/14416711 = now;

// Prune entries older than 24h
const TWENTY_FOUR_H = 24 * 60 * 60 * 1000;
for (const [key, ts] of Object.entries(staticData.processedGuids)) {
  if (now - ts > TWENTY_FOUR_H) delete staticData.processedGuids[key];
}

return [$input.item];

Prepare LLM Prompt:

It builds the prompt that will be sent to the AI model. It includes the error text, iFlow name, credential name, and the last five failures for that specific iFlow stored in workflow static data. This failure history is what allows the LLM to detect recurring patterns over time, for example noticing that the same iFlow has failed four times in 30 days always on the same day of the week.

// Builds the LLM prompt with full error context.
// Maintains a per-iFlow failure history (last 5) in static data.

const item = $input.item.json;
const reg  = item._registry_entry || {};

const staticData = $getWorkflowStaticData('global');
if (!staticData.failureHistory) staticData.failureHistory = {};
const iflow = item.iflow_name;
const hist  = staticData.failureHistory[iflow] || [];
hist.unshift({ ts: item.failure_timestamp, err: (item.error_text||'').substring(0,120) });
if (hist.length > 5) hist.pop();
staticData.failureHistory[iflow] = hist;
const historyText = hist.slice(1).map((h,i) => '  '+(i+2)+'. ['+h.ts+'] '+h.err).join('\n') || '  None';

const prompt="You are an SAP Integration Suite expert.\n"
  + 'Analyse this SAP Cloud Integration iFlow failure and return ONLY valid JSON with no other text.\n\n'
  + 'iFlow name: ' + item.iflow_name + '\n'
  + 'Credential name: ' + (item.credential || 'unknown') + '\n'
  + 'Error text: ' + (item.error_text || 'No error text provided') + '\n'
  + 'Recent failures on this iFlow:\n' + historyText + '\n\n'
  + 'Return this exact JSON structure:\n'
  + '{\n'
  + '  "error_type": "CREDENTIAL_EXPIRED or TRANSIENT or RATE_LIMITED or CERT_ERROR or MAPPING_ERROR or BAPI_EXCEPTION or UNKNOWN",\n'
  + '  "healing_action": "credential_heal or exponential_backoff or wait_and_retry or escalate",\n'
  + '  "confidence": ,\n'
  + '  "risk_level": "LOW or MEDIUM or HIGH",\n'
  + '  "safe_to_auto_heal": ,\n'
  + '  "plain_english": "",\n'
  + '  "likely_cause": "",\n'
  + '  "recommended_action": "",\n'
  + '  "operator_message": "",\n'
  + '  "pattern_detected": ""\n'
  + '}';

if (!staticData.attempts) staticData.attempts = {};
const guid = item.message_guid;
staticData.attemptshttps://community.sap.com/t5/technology-blog-posts-by-sap/sap-cloud-integration-ci-cpi-self-healing-iflows-with-n8n-and-ai/ba-p/14416711 = (staticData.attemptshttps://community.sap.com/t5/technology-blog-posts-by-sap/sap-cloud-integration-ci-cpi-self-healing-iflows-with-n8n-and-ai/ba-p/14416711 || 0) + 1;
const maxAttempts = reg.max_attempts || 3;
const attemptCount = staticData.attemptshttps://community.sap.com/t5/technology-blog-posts-by-sap/sap-cloud-integration-ci-cpi-self-healing-iflows-with-n8n-and-ai/ba-p/14416711;

return [{ json: Object.assign({}, item, { _llm_prompt: prompt, attempt_count: attemptCount, max_attempts: maxAttempts }) }];

LLM Classify Error:

The LLM Classify Error node sends the prepared prompt to OpenAI GPT-4o-mini and receives a structured JSON response back. The response contains the error type, confidence score, risk level, a plain-English explanation of what went wrong, the likely cause, a recommended action, and any recurring pattern detected across previous failures.

Adarshrao_0-1781186752607.Png

AI Confidence Teams Card:

Inform the Operations personnel/team about the failure via Microsoft Teams Channel with iFlow details.

JSON Body:

{
  "text": "🔐 **Credential Rotation — Approval Required**\n\n**iFlow:** {{ $json.iflow_name }}\n**Message GUID:** {{ $json.message_guid }}\n\n---\n\n🤖 **AI Analysis** *({{ $json.confidence }}% confidence | Risk: {{ $json.risk_level }})*\n\n**What went wrong:** {{ $json.plain_english }}\n\n**Likely cause:** {{ $json.likely_cause }}\n\n**Recommended action:** {{ $json.recommended_action }}\n\n{{ $json.pattern_detected ? '⚠️ **Pattern detected:** ' + $json.pattern_detected + '\\n\\n' : '' }}---\n\n✅ Please review the details above and approve or reject the credential rotation in the next card."
}

Trigger Healer:

This is used to trigger “Cloud Integration Self Healer – Credential Healer” workflow.

JSON body:

{
  "message_guid":      "={{ $('Parse LLM Classification').item.json.message_guid }}",
  "iflow_name":        "={{ $('Parse LLM Classification').item.json.iflow_name }}",
  "error_text":        "={{ $('Parse LLM Classification').item.json.error_text }}",
  "credential_name":   "={{ $('Parse LLM Classification').item.json._registry_entry.credential_name }}",
  "replay_allowed":    "={{ $('Parse LLM Classification').item.json._registry_entry.replay_allowed }}",
  "max_attempts":      "={{ $('Parse LLM Classification').item.json._registry_entry.max_attempts }}",
  "heal_credential":   "={{ $('Parse LLM Classification').item.json._registry_entry.heal_credential }}",
  "confidence":        "={{ $('Parse LLM Classification').item.json.confidence }}",
  "plain_english":     "={{ $('Parse LLM Classification').item.json.plain_english }}",
  "failure_timestamp": "={{ $('Parse LLM Classification').item.json.failure_timestamp }}"
}

 

3. Cloud Integration Self Healer – Credential Healer

The Credential Healer workflow is triggered by the Detector workflow after the LLM classifies a credential failure. It sends a Teams approval card to the operator, waits for human approval, fetches the correct password from environment variables, and patches the CPI Security Material via the CPI API.

N8N1.Png

Credential Healer Webhook:

Receives the request from Detector workflow.

Adarshrao_1-1781187733620.Png

Identify Credential:

This node reads the credential name from the incoming payload sent by the Detector workflow. It cleans the value by stripping any leading characters added by n8n during data transfer, and generates a unique approval ID that will be used to track this specific healing session.

Java Script:

// Reads the flat credential_name field sent by the Detector workflow.
// Falls back to _registry_entry.credential_name for backwards compatibility.

const raw  = $input.item.json;
const item = raw.body && typeof raw.body === 'object' && raw.body.iflow_name
             ? raw.body
             : raw.iflow_name
             ? raw
             : raw.body || raw;

// credential_name arrives as a flat field from the Detector
const credentialName = item.credential_name
                    || item._registry_entry?.credential_name
                    || item.credential;

if (!credentialName) {
  throw new Error('No credential_name found. Ensure Trigger Healer sends credential_name as a flat field.');
}

return [{ json: Object.assign({}, item, {
  _credential_name: credentialName,
  _approval_id: 'approval_' + (item.message_guid || 'unknown') + '_' + Date.now()
}) }];

Build Approval Card:

This node constructs a Microsoft Teams MessageCard object in JavaScript with the iFlow name, credential name, AI-generated explanation, confidence score, and clickable Approve and Reject links. The approve and reject URLs use n8n's built-in $execution.resumeUrl variable which points to the current paused execution, ensuring the correct workflow instance resumes when the operator clicks either button.

Java script:

const item       = $input.item.json;
const approveUrl = $execution.resumeUrl + '&action=approve';
const rejectUrl  = $execution.resumeUrl + '&action=reject';

const card = {
  "@type": "MessageCard",
  "@context": "http://schema.org/extensions",
  "themeColor": "FF8C00",
  "summary": "Credential Rotation Approval Required",
  "sections": [{
    "activityTitle": "Credential Rotation - Approval Required",
    "facts": [
      { "name": "iFlow",        "value": item.iflow_name        || "unknown" },
      { "name": "Credential",   "value": item._credential_name  || "unknown" },
      { "name": "What went wrong", "value": item.plain_english  || item.error_text || "unknown" },
      { "name": "AI Confidence",   "value": (item.confidence    || "N/A") + "%" }
    ]
  }],
  "potentialAction": [
    {
      "@type": "OpenUri",
      "name": "Approve and Heal",
      "targets": [{ "os": "default", "uri": approveUrl }]
    },
    {
      "@type": "OpenUri",
      "name": "Reject",
      "targets": [{ "os": "default", "uri": rejectUrl }]
    }
  ]
};

return [{ json: { _card: JSON.stringify(card), _approve_url: approveUrl, _reject_url: rejectUrl } }];

Send Teams Approval Card:

This card is used to request the operation team to approve or reject the credential rotation

Adarshrao_2-1781188133269.Png

Wait for Human Approval:

This node pauses the workflow execution for up to 2 hours until the operator clicks either the Approve or Reject link in the Teams card, which resumes the execution and passes the chosen action as a query parameter.

Adarshrao_3-1781188277991.Png

Approved:

Adarshrao_4-1781188326451.Png

Rejected – Teams Alert:

This node is used to alert the operations team if the credential rotation is rejected.

JSON body:

{
  "text": "❌ Credential rotation  **REJECTED** - Manual Intervention Required\n\n**iFlow**{{ $('Identify Credential').item.json.iflow_name }}\n**Credential**{{ $('Identify Credential').item.json._credential_name }}\n\n Please update the credential manually in CPI Security Material and request source system to resend the message."
}

Fetch New Credential:

This node reads the correct username and password from n8n environment variables using the credential name as a key, following the naming convention N8N_CRED_{CREDENTIAL_NAME_UPPERCASE}_USER and N8N_CRED_{CREDENTIAL_NAME_UPPERCASE}_PASSWORD.

const item     = $('Identify Credential').item.json;
const rawCred  = (item._credential_name || '').replace(/^=/, '').trim();
const credName = rawCred.toUpperCase().replace(/-/g, '_').replace(/ /g, '_');

const user     = $env['N8N_CRED_' + credName + '_USER'];
const password = $env['N8N_CRED_' + credName + '_PASSWORD'];

if (!user || !password) {
  throw new Error(
    'Credential not found in environment. Add N8N_CRED_' + credName + '_USER and N8N_CRED_' + credName + '_PASSWORD to your .env file.'
  );
}

return [{ json: Object.assign({}, item, {
  _new_user:     user,
  _new_password: password
}) }];

Clean Credentials:

This node is used to remove any leading characters from the credential name added by n8n.

Java script:

const prevItem  = $('Fetch New Credential').item.json;
const cleanCred = (prevItem._credential_name || '').replace(/^=/, '').trim();

return [{ json: Object.assign({}, prevItem, {
  _credential_name: cleanCred
}) }];

Patch CPI Security Material:

This node is used to perform PUT operation using the standard SAP Security Content API

https://api.sap.com/api/SecurityContent/overview

Pic_N8N3.Png

JSON body

{
  "Name": "{{ $json._credential_name }}",
  "Kind": "default",
  "Description": "Updated by n8n self-healer",
  "User": "{{ $json._new_user }}",
  "Password": "{{ $json._new_password }}"
}

Verify CPI Patch:

This node is used to check the response of the CPI Patch.

Java script:

const resp = $input.item.json;
const statusCode = resp.statusCode || resp.$response?.statusCode || 200;
const success = statusCode >= 200 && statusCode < 300;

return [{ json: Object.assign({}, resp, {
  _cpi_patch_success: success,
  _cpi_patch_status:  statusCode
}) }];

Patch Ok?:

Adarshrao_0-1781189222255.Png

Send Teams Success Confirmation:

JSON body:

{
  "text": "✅ Credential Rotated **Successfully**\n\n**iFlow**{{ $('Identify Credential').item.json.iflow_name }}\n**Credential**{{ $('Identify Credential').item.json._credential_name }}\nNew password applied to CPI Security Material.\n\nNext step: Ask the source system to resend the failed message."
}

Send Teams Failure Confirmation:

JSON body:

{
  "text": "❌ Credential Patch FAILED - Manual Intervention Required\n\n**iFlow**{{ $('Identify Credential').item.json.iflow_name }}\n**Credential**{{ $('Identify Credential').item.json._credential_name }}\n**CPI API Status**{{ $('Identify Credential').item.json._cpi_patch_status }}\n\nThe automatic credential rotation failed. Please update the credential manually in CPI Security Material and replay the message from CPI Message Monitor.\n\n**Message GUID**{{ $('Identify Credential').item.json.message_guid }}"
}

Step 7: Expose n8n Using ngrok

SAP Cloud Integration (CPI) runs in the cloud. To call your local n8n it needs a public URL. Use ngrok:

brew install ngrok/ngrok/ngrok

ngrok config add-authtoken YOUR_TOKEN

ngrok http 5678

Copy the https URL shown by ngrok (example: https://abc123.ngrok-free.app).

Warning:  ngrok free tier gives a different URL every time it restarts. Update the CPI iFlow HTTP adapter URL whenever ngrok restarts.

To test the set up, I have created a simple iFlow in Cloud Integration.

Adarshrao_0-1781189651043.Png

If have used a security material named “mock_bank_basic_auth“. I have configured our n8n mock bank api workflow as the receiver. Since it is self-hosted, BTP can't reach the localhost. Hence, I have used ngrok and it gave me the host as “https://parchment-purely-zoologist.ngrok-free.dev/webhook/mock-bank-api”

Adarshrao_1-1781189890724.Png

In the Exception subprocess, I am calling n8n “Cloud Integration Self Healer – Detector and Classifier” workflow and as an input I am using the below groovy script to build the error payload.

import com.sap.gateway.ip.core.customdev.util.Message
import groovy.json.JsonOutput
import org.apache.camel.Exchange
import org.apache.camel.builder.SimpleBuilder

def Message processData(Message message) {
    def headers  = message.getHeaders()
    def props    = message.getProperties()
    Exchange exchange = message.exchange

    // Get iFlow runtime ID via camelId
    def iflowId = SimpleBuilder.simple('${camelId}')
            .evaluate(exchange, String)
            ?.toString()
            ?.trim() ?: 'unknown'

    def errorMsg = 'Unknown error'
    try {
        errorMsg = props.get('CamelExceptionCaught')?.getMessage() ?: 'Unknown error'
    } catch (Exception e) {}

    def payload = [
        message_guid      : headers.get('SAP_MessageProcessingLogID') ?: 'unknown',
        iflow_name        : iflowId,
        error_text        : errorMsg,
        credential        : props.get('credentialName')               ?: '',
        correlation_id    : headers.get('SAP_MessageProcessingLogID') ?: '',
        failure_timestamp : new Date().format("yyyy-MM-dd'T'HH:mm:ss")
    ]

    message.setBody(JsonOutput.toJson(payload))
    message.setHeader('Content-Type', 'application/json')
    return message
}

With the correct credential, I receive the response as below.

Adarshrao_2-1781189945579.PngIf I edit the security material with the wrong password then because HTTP 401 error, my exception subprocess will call n8 workflow and I receive a Approval card in MS Teams as below.

N8N_Ask_Approval_Card.png

If I Reject then I receive an alert as below 

Adarshrao_4-1781190448325.Png

If I Approve then I receive an alert as below and the correct/new credentials are updated in Cloud Integration Security material for the credential “mock_bank_basic_auth”.

Adarshrao_5-1781190538534.Png

Replace Demo Credentials with a Secrets Vault

For the PoC, credentials are stored as environment variables in the n8n configuration file. For production, replace the Fetch New Credential code node with an HTTP call to Azure Key Vault or HashiCorp Vault. This is a single node change. The rest of the workflow stays identical.

 

Security Considerations

Risk

Mitigation

Credential patching without audit trail

n8n logs every execution with timestamp and approval action

n8n webhook exposed publicly via ngrok

Add IP allowlisting in ngrok or use a reverse proxy with authentication

This blog presented a concept for self-healing SAP Cloud Integration (CPI) iFlows using n8n and AI. The idea is to detect credential failures instantly, use an LLM to understand the root cause in context, notify the operator with a plain-English explanation, and automatically fix the credential configuration after human approval.

This is a concept and a starting point. Every organization has different requirements, different tooling preferences, and different security policies. The patterns shown here can be adapted, extended, and built upon. Some teams may want to add a secrets vault. Others may want to extend the error classifier to handle additional failure types. Others may want to replace n8n with SAP Build Process Automation as the orchestration layer. The core idea remains the same regardless of which tools you choose.

I hope this sparks some ideas. If you build on this concept or extend it in an interesting direction, I would love to hear about it in the comments.

Source link

Leave a Reply

Your email address will not be published. Required fields are marked *