$binary.audio becomes undefined in Code node after successful binary upload via Webhook node

<!-- Hey! The fastest way to find solutions is by using the 🔎 search function at the upper right.
If your question hasn't been asked before, please follow the template below. Skip the questions that are not relevant to you. -->

[Workflow Purpose]

  • To receive a meeting recording (or uploaded audio file),

  • Automatically perform speech-to-text (STT) transcription and summarization,

  • And send the results via email or provide a download link if needed.


[Data Flow and Processing Steps]

  1. User Interface

    • Users can record audio directly or upload an audio file via a web UI (HTML/JS).

    • Email input is required, and the uploaded/recorded file is sent to the n8n Webhook node.

  2. Webhook: Upload (POST /meeting/upload)

    • Receives the uploaded audio file as binary data.

    • The binary key is initially data.

  3. Code: Find binary data

    • Changes the binary key from data to audio.

    • Ensures all subsequent nodes can reference the binary as audio.

  4. Code: Validate & Pick Binary1

    • Verifies that the audio binary exists and checks file size/extension/MIME type.

    • Normalizes the file name and MIME type.

  5. HTTP: Whisper STT (OpenAI, etc.)

    • Sends the audio binary to an external STT API (e.g., OpenAI Whisper) for transcription.
  6. Code: Static Upsert Part & Merge

    • If the upload is split into multiple parts, temporarily stores/merges each part’s text in workflow static data.

    • Once all parts are uploaded, combines the full text.

  7. Code: Check Text Length

    • Checks the total text length and word count to determine if it’s suitable for summarization.
  8. HTTP: GPT Summarize (Chat Completions)

    • Sends the full text to the OpenAI GPT API to generate a summary.
  9. Code: Pick Summary

    • Organizes the summary result and metadata.
  10. Set: Carry Payload & Send Email

    • Sends the summary and full text to the user via email.

    • If email delivery fails, provides a JSON response for direct download in the browser.


[Data Creation and Transfer Summary]

  • Binary data: The file is received by the Webhook, the key is changed to audio in a Code node, and all subsequent nodes reference it as audio.

  • JSON data: Each step passes along necessary metadata (email, group_id, part_index, etc.) and the text/summary results as JSON.

  • Error handling: If validation fails at any step, the workflow stops with an error message or returns a specific error response.

Describe the problem/error/question

  1. Webhook node receives binary data correctly (key: “data”, MIME: multipart/form-data, filename and size normal).

  2. In the first Code node, I remap the binary key from “data” to “audio” using:

    javascript

    return [{
      json: $json,
      binary: { audio: $binary.data['data'] } // Assuming input binary key is 'data'
    }];
    
  3. In the next Code node, $binary.audio is undefined, causing an error: “Binary data is empty or invalid. Size: undefined”.

What is the error message (if any)?

Problem in node ‘Code: Validate & Pick Binary1‘

No binary data found in input. Check previous node output. [line 2]

Please share your workflow

Share the output returned by the last node

<!-- If you need help with data transformations, please also share your expected output. -->
[Workflow Structure]
- Webhook → Code: Find binary data (remaps to 'audio') → Code: Validate & Pick Binary1 (references $binary.audio, which is undefined).
- No intermediate nodes that could lose binary data (e.g., no Split, Merge, IF).
- All nodes are set to "runOnceForEachItem" execution mode.
[Attempted Solutions]
- Changed the return format in Code nodes to a single object instead of an array.
- Ensured all nodes have the same execution mode ("runOnceForEachItem").
- Verified binary data in each node's input/output tabs; the binary exists in the Webhook output but disappears after remapping.

Information on your n8n setup

  • n8n version: 1.109.1-exp.0 (Cloud)
  • Database (default: SQLite): -
  • n8n EXECUTIONS_PROCESS setting (default: own, main): -
  • Running n8n via (Docker, npm, n8n cloud, desktop app): n8n cloud
  • Operating system: -

Whenever you are assigning binary data to a variable, you want to do it like this:

const bin = $input.item.binary || {};

not like this:

const bin = $binary || {};

Here is the changed code that works for me, you can copy these 2 nodes directly from here and paste them in your flow to test them out:

1 Like

Hey @juhyeon_PARK hope all is good. First thing I would try is use a v2 version of the Webhook node,

which allows to set the name of the binary property name (1). Unfortunately the node always appends the binary index to the filename, even if there is only one file present.

If this is all you need, I would do

function renameBinary(obj, from, to) {
  if (!(from in obj) || from === to) return obj;
  obj[to] = obj[from];
  delete obj[from];
  return obj;
}

renameBinary($input.item.binary, "data", "audio");

return $input.item

for the second code, see if you like the following:

const audio = $input.item?.binary?.audio;
if (!audio) {
  throw new Error('No binary "audio" found. Please check the previous node.');
}

const EXT_TO_MIME = {
  webm: 'audio/webm',
  mp3:  'audio/mpeg',
  mpeg: 'audio/mpeg',
  mpga: 'audio/mpeg',
  mp4:  'audio/mp4',
  wav:  'audio/wav',
  ogg:  'audio/ogg',
  oga:  'audio/ogg',
  flac: 'audio/flac',
  m4a:  'audio/m4a',
};

const ALIAS_TO_CANON = { mpeg: 'mp3', mpga: 'mp3', oga: 'ogg' };
const OK = new Set(Object.keys(EXT_TO_MIME));

const name = audio.fileName ?? 'audio';
const dot = name.lastIndexOf('.');
const base = dot > -1 ? name.slice(0, dot) : name;
const rawExt = (dot > -1 ? name.slice(dot + 1) : '').toLowerCase();

const fromMime = (() => {
  const m = (audio.mimeType || '').toLowerCase();
  if (!m) return '';
  
  if (m.includes('webm')) return 'webm';
  if (m.includes('mp4'))  return 'mp4';
  if (m.includes('mpeg') || m.includes('mp3') || m.includes('mpga')) return 'mp3';
  if (m.includes('wav'))  return 'wav';
  if (m.includes('ogg') || m.includes('oga')) return 'ogg';
  if (m.includes('flac')) return 'flac';
  if (m.includes('m4a'))  return 'm4a';
  return '';
})();

const pick = (ext) => {
  if (!ext) return '';
  const canon = ALIAS_TO_CANON[ext] ?? ext;
  return OK.has(canon) ? canon : '';
};

const finalExt = pick(rawExt) || pick(fromMime) || 'webm';
const finalMime = EXT_TO_MIME[ALIAS_TO_CANON[finalExt] ?? finalExt] || audio.mimeType || 'application/octet-stream';

audio.fileName = `${base || 'audio'}.${finalExt}`;
audio.mimeType = finalMime;

return { json: { _binKey: 'audio' }, binary: { audio } };

One problem in your second code was that the return of the code node with “Run Once for Each Item” needs to be an object, not an array. Also the above code has some minor improvements, namely - it uses a single source of truth - one EXT_TO_MIME map (+ small alias map). No big switch/if ladders. The parseing is a bit tighter, no need for extra helpers for name splitting. Clear priority for the final extension - filename ext (if valid) over mime-derived ext over default webm.

Final:

2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.