Zie619/n8n-workflows · error · Error

Initial Vector 'data' is undefined or missing.

Error message

Initial Vector 'data' is undefined or missing.

What it means

Thrown by the 'Encrypt Return' Code node in a WhatsApp Flows response workflow. The node reads initialVector from the upstream 'move to base64' node and expects either a string, Buffer, or byte array after the `.data || initialVector` fallback. 'data' is undefined or missing when the value read from `$('move to base64').first().json.initialVector` plus its `.data` property both evaluate falsy (undefined, null, empty). Note the guard is partially dead code: because of the `|| initialVector` fallback, this branch only fires when initialVector itself is falsy, which the earlier check at the top of the node already excludes in most runs.

Source

Thrown at workflows/Code/0924_Code_Respondtowebhook_Process_Webhook.json:220

        -1320,
        860
      ],
      "parameters": {
        "jsCode": "const crypto = require(\"crypto\");\n\nconst privateKey = `-----BEGIN PRIVATE KEY-----\n[INSERT YOUR KEY HERE]\n-----END PRIVATE KEY-----`;\n\n// Convert input buffers\nconst encryptedAesKeyBuffer = Buffer.from($json.encryptedAesKey.data);\nconst initialVector = Buffer.from($json.initialVector.data);\nconst encryptedFlowData = Buffer.from($json.encryptedFlowData.data);\n\n// Check if encrypted AES key, IV, and encrypted flow data exist\nif (!encryptedAesKeyBuffer || !initialVector || !encryptedFlowData) {\n  throw new Error(\"Missing required data (encrypted AES key, IV, or flow data)\");\n}\n\n// Decrypt AES key using RSA\nconst decryptedKey = crypto.privateDecrypt(\n  {\n    key: privateKey,\n    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,\n    oaepHash: \"sha256\",\n  },\n  encryptedAesKeyBuffer\n);\n\n// Ensure AES key is exactly 16 bytes (AES-128 requires it)\nconst aesKey = decryptedKey.slice(0, 16);\nif (aesKey.length !== 16) {\n  throw new Error(\"Invalid AES Key length\");\n}\n\n// Handle initialization vector (IV): If needed, flip the IV bits (standardize behavior)\nconst standardizedIv = Buffer.from(initialVector);\nif (standardizedIv.length !== 16) {\n  throw new Error(\"Invalid IV length, must be 16 bytes\");\n}\n\n// Extract the last 16 bytes as the authentication tag (GCM uses 16-byte tags)\nconst authTag = encryptedFlowData.slice(-16);\nconst encryptedDataWithoutTag = encryptedFlowData.slice(0, -16);\n\n// AES Decryption\nconst decipher = crypto.createDecipheriv(\"aes-128-gcm\", aesKey, standardizedIv);\ndecipher.setAuthTag(authTag);\n\nlet decrypted;\ntry {\n  decrypted = Buffer.concat([\n    decipher.update(encryptedDataWithoutTag),\n    decipher.final(),\n  ]);\n} catch (error) {\n  throw new Error(\"Decryption failed: \" + error.message);\n}\n\nreturn [{ \n  decryptedPayload: decrypted.toString(\"utf-8\"),\n  aesKey: aesKey.toString(\"base64\")\n}];\n"
      },
      "typeVersion": 2,
      "notes": "This code node performs automated tasks as part of the workflow."
    },
    {
      "id": "17c055f3-c278-48c4-89d4-d305a35bc526",
      "name": "Encrypt Return",
      "type": "n8n-nodes-base.code",
      "position": [
        -200,
        760
      ],
      "parameters": {
        "jsCode": "const crypto = require(\"crypto\");\n\n// Access initial_vector from the correct path\nconst initialVector = $('move to base64').first().json.initialVector;\n\nif (!initialVector) {\n    throw new Error(\"Initial Vector is undefined or missing.\");\n}\n\n// Check if 'data' is a property of initialVector\nconst ivData = initialVector.data || initialVector; // Fallback to initialVector if no 'data' property\n\nif (!ivData) {\n    throw new Error(\"Initial Vector 'data' is undefined or missing.\");\n}\n\n// Check for various formats of initialVector\nlet ivBuffer;\nif (typeof ivData === \"string\") {\n    ivBuffer = Buffer.from(ivData, 'base64');\n} else if (Buffer.isBuffer(ivData)) {\n    ivBuffer = ivData;\n} else if (Array.isArray(ivData)) {\n    ivBuffer = Buffer.from(ivData);\n} else {\n    throw new Error(\"Initial Vector 'data' is in an unsupported format.\");\n}\n\n// Invert Initialization Vector\nconst invertedIV = Buffer.from(ivBuffer.map((b) => ~b & 0xFF)); // Ensure the result stays a valid byte\n\n// Access AES Key from the correct path\nconst aesKeyBase64 = $('Decryption Code').first().json.aesKey || \"\";\nif (!aesKeyBase64) {\n    throw new Error(\"AES Key is missing.\");\n}\n\nconst aesKey = Buffer.from(aesKeyBase64, \"base64\");\n\n// Extract data from the input with proper error handling\nlet date = \"2025-03-14\"; // Default fallback date\nlet startTimes = []; // Default empty array for start times\n\n// Check if $json exists and has the expected structure\nif ($json) {\n    // Check if $json is an array\n    if (Array.isArray($json) && $json.length > 0) {\n        const appointmentData = $json[0];\n        if (appointmentData && appointmentData.appointment_date) {\n            date = appointmentData.appointment_date;\n        }\n        if (appointmentData && Array.isArray(appointmentData.start_times)) {\n            startTimes = appointmentData.start_times;\n        }\n    } else if ($json.appointment_date) {\n        // If $json is not an array but has appointment_date directly\n        date = $json.appointment_date;\n        if (Array.isArray($json.start_times)) {\n            startTimes = $json.start_times;\n        }\n    }\n}\n\n// Log the structure of $json for debugging\nconsole.log(\"Input JSON structure:\", JSON.stringify($json, null, 2));\n\n// Ensure we have time slots (use defaults if none found)\nif (!startTimes.length) {\n    console.log(\"No time slots found in input, using defaults\");\n    startTimes = [\"12:00:00\", \"12:30:00\", \"13:30:00\", \"14:00:00\"];\n}\n\n// Map the time slots to the required format\nconst timeSlots = startTimes.map((timeString, index) => ({\n    id: `time_${index + 1}`,\n    title: timeString\n}));\n\n// Map the date slots for each time slot\nconst dateSlots = [{\n    id: \"date_1\",\n    title: date\n}];\n\n// Define the response data with the extracted time and date\nconst responseData = {\n    status: \"active\",\n    time: timeSlots,\n    date: dateSlots\n};\n\n// Define the flow_token (accessed from the correct path)\nconst flowToken = $('Json Parser').first().json.flow_token || \"\"; // Fetch the flow_token dynamically from the path\n\nif (!flowToken) {\n    throw new Error(\"Flow token is missing.\");\n}\n\n// Define the next screen (this should be based on your flow logic)\nconst nextScreen = \"APPOINTMENT\"; // You can set this dynamically depending on the flow\n\n// Define Response Message (updated to match the required response format)\nconst responseMessage = JSON.stringify({\n    version: \"3.0\", // Fixed version as per your requirements\n    action: \"data_exchange\", // Since we're responding to a data exchange request\n    screen: nextScreen, // The next screen that the user will be redirected to\n    data: responseData, // Data to send back (includes the time and date)\n    flow_token: flowToken, // Flow token for session identification\n});\n\n// Encrypt Response using AES-GCM\nconst cipher = crypto.createCipheriv(\"aes-128-gcm\", aesKey, invertedIV);\nlet encryptedResponse = Buffer.concat([\n    cipher.update(responseMessage, \"utf-8\"),\n    cipher.final()\n]);\n\n// Get the authentication tag\nconst authTag = cipher.getAuthTag();\n\n// Append the authentication tag to the encrypted response\nconst result = Buffer.concat([encryptedResponse, authTag]);\n\n// Encode the entire response as Base64\nconst base64Response = result.toString(\"base64\");\n\n// Return the Base64-encoded response as the body\nreturn [{ body: base64Response }];\n"
      },
      "typeVersion": 2,
      "notes": "This code node performs automated tasks as part of the workflow."
    },
    {
      "id": "412f55e3-5867-4e65-a494-3e3bf991d59c",
      "name": "Encrypt Return1",
      "type": "n8n-nodes-base.code",
      "position": [
        -200,
        1000
      ],
      "parameters": {
        "jsCode": "const crypto = require(\"crypto\");\n\nconst jsonData = items;\n\n// Parse the decryptedPayload string into a JSON object\nconst decryptedPayload = JSON.parse(jsonData[0].json.originalPayload.decryptedPayload);\n\n// Extract the seats array\nconst seats = decryptedPayload.data.seats;\n\nif (!seats || !Array.isArray(seats) || seats.length === 0) {\n    throw new Error(\"Seats data is missing or invalid.\");\n}\n\n// Access initial_vector from the correct path\nconst initialVector = $('move to base64').first().json.initialVector;\nif (!initialVector) {\n    throw new Error(\"Initial Vector is undefined or missing.\");\n}\n\nconst ivData = initialVector.data || initialVector;\nif (!ivData) {\n    throw new Error(\"Initial Vector 'data' is undefined or missing.\");\n}\n\nlet ivBuffer;\nif (typeof ivData === \"string\") {\n    ivBuffer = Buffer.from(ivData, 'base64');\n} else if (Buffer.isBuffer(ivData)) {\n    ivBuffer = ivData;\n} else if (Array.isArray(ivData)) {\n    ivBuffer = Buffer.from(ivData);\n} else {\n    throw new Error(\"Initial Vector 'data' is in an unsupported format.\");\n}\n\nconst invertedIV = Buffer.from(ivBuffer.map((b) => ~b & 0xFF));\n\n// Access AES Key from the correct path\nconst aesKeyBase64 = $('Decryption Code').first().json.aesKey || \"\";\nif (!aesKeyBase64) {\n    throw new Error(\"AES Key is missing.\");\n}\nconst aesKey = Buffer.from(aesKeyBase64, \"base64\");\n\n// Define the response data with the extracted seats\nconst responseData = {\n    status: \"active\",\n    seats: seats.map((seat, index) => ({\n        id: `seat_${index + 1}`,\n        title: seat\n    }))\n};\n\n// Define the flow_token\nconst flowToken = $('Json Parser').first().json.flow_token || \"\";\nif (!flowToken) {\n    throw new Error(\"Flow token is missing.\");\n}\n\nconst nextScreen = \"SUMMARY\";\n\nconst responseMessage = JSON.stringify({\n    version: \"3.0\",\n    action: \"data_exchange\",\n    screen: nextScreen,\n    data: responseData,\n    flow_token: flowToken,\n});\n\n// Encrypt Response using AES-GCM\nconst cipher = crypto.createCipheriv(\"aes-128-gcm\", aesKey, invertedIV);\nlet encryptedResponse = Buffer.concat([\n    cipher.update(responseMessage, \"utf-8\"),\n    cipher.final()\n]);\n\nconst authTag = cipher.getAuthTag();\nconst result = Buffer.concat([encryptedResponse, authTag]);\nconst base64Response = result.toString(\"base64\");\n\n// Return the encrypted response\nreturn [{ body: base64Response }];\n"
      },
      "typeVersion": 2,
      "notes": "This code node performs automated tasks as part of the workflow."
    },

View on GitHub (pinned to 94007c1445)

Solutions

  1. Inspect the actual shape: add console.log(JSON.stringify($('move to base64').first().json.initialVector)) before the checks and run once to see what arrives.
  2. Ensure the incoming webhook body always contains encrypted_flow_data, encrypted_aes_key, and initial_vector, and that 'move to base64' executed on this branch.
  3. Read the raw field directly instead of the serialized node output, e.g. const ivData = $json.body?.initial_vector, and Buffer.from(ivData, 'base64') locally.
  4. Standardize the upstream node to return initialVector as a base64 STRING (initialVector: ($json.body?.initial_vector || '')) so the downstream format checks are deterministic.

Example fix

// before
const initialVector = $('move to base64').first().json.initialVector;
if (!initialVector) { throw new Error("Initial Vector is undefined or missing."); }
const ivData = initialVector.data || initialVector;
if (!ivData) { throw new Error("Initial Vector 'data' is undefined or missing."); }

// after - read raw webhook field, single deterministic format
const ivB64 = $('Webhook').first().json.body?.initial_vector;
if (typeof ivB64 !== 'string' || ivB64.length === 0) {
  throw new Error("Initial Vector is undefined or missing.");
}
const ivBuffer = Buffer.from(ivB64, 'base64');
if (ivBuffer.length !== 16) throw new Error("IV must be 16 bytes");
Defensive patterns

Strategy: validation

Validate before calling

// before building the cipher, in the encrypt node
const ivRaw = $('move to base64').first().json.initialVector;
const ivData = ivRaw?.data ?? ivRaw;
const ok = typeof ivData === 'string' ? ivData.length > 0
  : Array.isArray(ivData) ? ivData.length === 16
  : Buffer.isBuffer(ivData) ? ivData.length === 16 : false;
if (!ok) throw new Error(`IV invalid: ${JSON.stringify(ivRaw)?.slice(0, 100)}`);

Type guard

const isUsableIV = (v) => {
  const d = v?.data ?? v;
  return (typeof d === 'string' && d.length > 0)
    || (Array.isArray(d) && d.length === 16)
    || Buffer.isBuffer(d);
};

Try / catch

try { ivBuffer = toBuffer(ivData); } catch (e) { throw new Error(`IV unusable: ${e.message}`); }

Prevention

When it happens

Trigger: The webhook request body lacked `initial_vector` (Buffer.from("", "base64") upstream produced an empty buffer that serializes to something falsy in the json payload); the 'move to base64' node did not execute before this node on the current branch (`.first()` returns a record without the initialVector key); or n8n serialized the Buffer differently across versions so neither a string nor a `{data:[...]}` object is present.

Common situations: Testing the workflow manually without supplying the WhatsApp Flows encrypted payload fields; replaying an execution whose 'move to base64' output is absent; branch ordering changes so 'Encrypt Return' runs before the base64 conversion node; n8n upgrade changing Buffer-to-JSON serialization in node output.

Related errors


AI-assisted analysis of Zie619/n8n-workflows@94007c1445 (2026-08-15). Data as JSON: /api/errors/7769912507465617. Report an issue: GitHub.