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

Describe the problem/error/question

I am building a workflow to process uploaded meeting audio files:

  • The user uploads an audio file via a Webhook node (binary data is present and visible in the node’s output as the key data).

  • In the next Code node, I rename the binary key from data to audio.

  • However, in the following Code node, $binary.audio is undefined, and I get an error that no binary data is found.

  • All Code nodes are set to “Run Once for Each Item” and return a single object (not an array).

  • There are no IF, Merge, or Split nodes between the Webhook and the Code nodes.

What is the error message (if any)?

No binary data found in input. Check previous node output.
No binary "audio" found. Please check the previous node.

Please share your workflow

(Select the nodes on your canvas and use the keyboard shortcuts CMD+C/CTRL+C and CMD+V/CTRL+V to copy and paste the workflow.)
{
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "meeting/upload",
        "responseMode": "responseNode",
        "options": {
          "binaryData": true
        }
      },
      "id": "facab62c-c379-4577-b13e-bf82af3a1616",
      "name": "Webhook: Upload (POST /meeting/upload)",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        832,
        -1552
      ],
      "webhookId": "c4287f5b-fa93-4500-90b5-8713420b6af3"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "6d3b179a-2ff8-4b65-b550-b395784104c9",
              "leftValue": "={{ $json.error || '' }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1488,
        -1200
      ],
      "id": "acbbbdd3-469d-4c26-9763-95d2f77697f7",
      "name": "IF: has error?"
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{$json.message || 'Bad Request'}}",
        "options": {
          "responseCode": 400,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/plain; charset=utf-8"
              }
            ]
          }
        }
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1744,
        -1008
      ],
      "id": "4673ea9d-c7f5-4ae1-9573-2a2a013b3d88",
      "name": "Respond: 400 Bad Request"
    },
    {
      "parameters": {
        "jsCode": "/**\n * 목적\n *  - 전사 텍스트를 workflow static data에 파트별로 임시 저장\n *  - 단어 수 계산 추가\n *  - TTL 추가: 1일 경과 시 자동 삭제\n */\n\nfunction textFromItem(j) {\n  if (Array.isArray(j.segments)) {\n    return j.segments.map(s => (s.text || '').trim()).join(' ').trim();\n  }\n  return (j.text || j.fullText || '').toString();\n}\n\nconst j = $json;\nconst group_id   = j.group_id;\nconst part_index = Number(j.part_index ?? 0);\nconst email      = j.worker_email || j._email || '';\nconst partText   = textFromItem(j);\n\nif (!group_id)  return [{ json: { error: 'missing group_id' } }];\nif (!email)     return [{ json: { error: 'missing email' } }];\nif (!partText)  return [{ json: { error: 'empty part text' } }];\n\nconst wordCount = partText.split(/\\s+/).filter(word => word.length > 0).length;  // 띄어쓰기로 단어 수 계산\n\nconst store = this.getWorkflowStaticData('global');\nif (!store.transcripts) store.transcripts = {};\nconst slot = store.transcripts[group_id] || { parts: {}, email, createdAt: Date.now(), totalWords: 0 };\nslot.email = slot.email || email;\nslot.parts[part_index] = partText;\nslot.totalWords += wordCount;  // 부분 단어 수 누적\n\nif (Date.now() - slot.createdAt > 86400000) {\n  delete store.transcripts[group_id];\n  return [{ json: { error: 'TTL expired for group_id: ' + group_id } }];\n}\n\nstore.transcripts[group_id] = slot;\n\nreturn [{\n  json: {\n    ...j,\n    _stored: true,\n    _debug: { keys: Object.keys(slot.parts).map(k=>Number(k)).sort((a,b)=>a-b), email: slot.email, wordCount },\n    totalWords: slot.totalWords  // 전체 단어 수\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2400,
        -1568
      ],
      "id": "f3e72549-61a9-49de-8ca8-9772db30984e",
      "name": "Code: Static Upsert Part"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const binaryKeys = Object.keys($binary || {});\nif (binaryKeys.length === 0) {\n  throw new Error('No binary data found in webhook payload. Check if the file was uploaded and the Webhook node has \"Binary Data\" enabled.');\n}\nconst firstBinaryKey = binaryKeys[0];\nconst firstBinaryData = $binary[firstBinaryKey];\nreturn {\n  json: {\n    fileInfo: firstBinaryData,\n    log: binaryKeys,\n    firstBinaryKey,\n    firstBinaryData,\n    ...$json\n  },\n  binary: {\n    audio: firstBinaryData\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1056,
        -1552
      ],
      "id": "d73b27f2-d74d-49fb-9858-89bb11fd1a4c",
      "name": "Code: Find binary data"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "if (!$binary || !Object.keys($binary).length) {\n  throw new Error('No binary data found in input. Check previous node output.');\n}\n\nconst bin = $binary || {};\nconst audio = bin.audio;\nif (!audio) {\n  throw new Error('No binary \"audio\" found. Please check the previous node.');\n}\n\n// 확장자/타입 보정 로직 (예시)\nconst OK_EXTS = ['flac','m4a','mp3','mp4','mpeg','mpga','oga','ogg','wav','webm'];\nfunction splitName(fname) {\n  if (!fname) return { base: 'audio', ext: '' };\n  const i = fname.lastIndexOf('.');\n  if (i < 0) return { base: fname, ext: '' };\n  return { base: fname.slice(0, i), ext: fname.slice(i + 1).toLowerCase() };\n}\nfunction extFromMime(m) {\n  if (!m) return null;\n  m = m.toLowerCase();\n  if (m.includes('webm')) return 'webm';\n  if (m.includes('mp4'))  return 'mp4';\n  if (m.includes('mpeg') || m.includes('mp3') || m.includes('mpga')) return 'mp3';\n  if (m.includes('wav'))  return 'wav';\n  if (m.includes('ogg') || m.includes('oga')) return 'ogg';\n  if (m.includes('flac')) return 'flac';\n  if (m.includes('m4a'))  return 'm4a';\n  return null;\n}\nfunction canonicalMime(ext) {\n  switch (ext) {\n    case 'webm': return 'audio/webm';\n    case 'mp4':  return 'audio/mp4';\n    case 'mp3':\n    case 'mpeg':\n    case 'mpga': return 'audio/mpeg';\n    case 'wav':  return 'audio/wav';\n    case 'ogg':\n    case 'oga':  return 'audio/ogg';\n    case 'flac': return 'audio/flac';\n    case 'm4a':  return 'audio/m4a';\n    default:     return audio.mimeType || 'application/octet-stream';\n  }\n}\nconst { base, ext } = splitName(audio.fileName || '');\nlet finalExt = ext;\nif (!finalExt || !OK_EXTS.includes(finalExt)) {\n  finalExt = extFromMime(audio.mimeType) || 'webm';\n}\naudio.fileName = `${base || 'audio'}.${finalExt}`;\naudio.mimeType = canonicalMime(finalExt);\n\nreturn [{ json: { _binKey: 'audio' }, binary: { audio } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1264,
        -1392
      ],
      "id": "031b96cc-ed25-48f1-a47a-57bd2a99ebdd",
      "name": "Code: Validate & Pick Binary1"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/audio/transcriptions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpBearerAuth",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "audio"
            },
            {
              "name": "model",
              "value": "whisper-1"
            },
            {
              "name": "filename",
              "value": "={{ $binary.audio.fileName }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2160,
        -1792
      ],
      "id": "2e4a7222-95c2-4506-b543-f20032673990",
      "name": "HTTP: Whisper : n8n",
      "alwaysOutputData": false,
      "executeOnce": false,
      "retryOnFail": false,
      "credentials": {
        "httpBearerAuth": {
          "id": "CzXJOc1jysXur2K6",
          "name": "Bearer Auth account"
        }
      }
    }
  ],
  "connections": {
    "Webhook: Upload (POST /meeting/upload)": {
      "main": [
        [
          {
            "node": "Code: Find binary data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: has error?": {
      "main": [
        [
          {
            "node": "HTTP: Whisper : n8n",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond: 400 Bad Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Static Upsert Part": {
      "main": [
        []
      ]
    },
    "Code: Find binary data": {
      "main": [
        [
          {
            "node": "Code: Validate & Pick Binary1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Validate & Pick Binary1": {
      "main": [
        [
          {
            "node": "IF: has error?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Whisper : n8n": {
      "main": [
        [
          {
            "node": "Code: Static Upsert Part",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "a254ea5216ea6cfbd8ee7fe4e961a7d4307ef34e8c99e3784178729684a073dc"
  }
}

Share the output returned by the last node

The last node (Code: Validate & Pick Binary1) returns the error above.
The previous node (Code: Find binary data) is supposed to output a single item with a binary key audio, but in the next node, $binary.audio is undefined.

Information on your n8n setup

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

Additional context

  • Webhook node output (binary tab):

    • Key: data

    • File name, mime type, and file size are all present and correct.

  • No data transformation nodes (IF, Merge, Split) between Webhook and Code nodes.

  • All Code nodes use “Run Once for Each Item” and return a single object.

I uploaded the audio file recorded in the browser to the n8n server in form-data format, and then I created a workflow like this to send it to WhisperAI via HTTP, but I don’t know how to preprocess the form-data to convert it into a format that WhisperAI can recognize. I’ve been testing this part for 3 days but haven’t been able to solve it. I need help.

Hello @juhyeon_PARK, welcome to the community!

The main issue is handling the binary data and referencing it correctly. I suggest reading the docs and using the AI on that page to ask how to do what you need:

You can also simplify the process, I don’t think having two different code nodes is the best approach..
However, I just fixed your workflow as it is, it should work now..

Please test it on your end and let me know if you run into any problems.

also, this is the openai docs for create transcription if you want to edit any parameters
https://platform.openai.com/docs/api-reference/audio/createTranscription

2 Likes

Thank you so much for your quick response!!!

I confirmed that the form-data is being processed without errors using the nodes you provided. I didn’t know that n8n has its own unique functions, so I was clueless about the root cause. I’m someone who doesn’t know how to code at all, so I was also unfamiliar with concepts like buffers or file streams. I’ll check out the n8n docs and OpenAI documentation that you sent as well. Thank you so, so much for your help!

Currently, I’m seeing an error in the “HTTP: Whisper : n8n1” node. The error is as follows:

Bad request - please check your parameters Invalid file format. Supported formats: ['flac', 'm4a', 'mp3', 'mp4', 'mpeg', 'mpga', 'oga', 'ogg', 'wav', 'webm']

Even though I modified the extension and MIME type in the previous Code node to be recognizable by Whisper, it doesn’t seem to be working correctly.

These are the supported formats

so your audio file must be one of them..

1 Like

Oh, I was already aware of that part, and that’s why, as you can see in the attached image, I kindly sent the audio file to Whisper with a *.webm extension!

It feels like there might be a little process I haven’t figured out yet—could you help me with that, please? :blush:

sure, from the screenshot it seems that the binary file is edited (There is not “view” button), did you edit anything in the code in the workflow above?

1 Like

I used the workflow nodes you sent me in your response exactly as they were.

I didn’t make any modifications.
Maybe same node exist in my workflow as “HTTP: Whisper : n8n” node changed to “HTTP: Whisper : n8n1”

Just create another workflow and test it. If you can also provide a sample of the webm file, I can test it on my end..
I also noticed that your code converts the videos to audio, but I’m not sure you actually need that, since the API only supports certain formats, you just need to pass the correct one.
No conversion is required on your side.

1 Like

If the issue is fixed please @juhyeon_PARK don’t forget to mark my reply as solution, thanks..

1 Like

If the issue is resolved, I’ll definitely switch it to mark it as solved!

This wasn’t mentioned in the posting, but I’ve written the code to use only audio files. The part about converting videos to audio wasn’t my intention—it was something ChatGPT added! :slight_smile:

As you suggested, I’ve been testing with audio files (*.webm) only.

I’ll provide a test file for you as you requested.
This is link for test webm file.

https://drive.google.com/file/d/1FXObjSo1iLKuPfXjQk7Jb1L43xh2zSwa/view?usp=drive_link

The MIME type of this file is video/webm, which is probably why it isn’t accepted, They likely mean webm files that only audio. That’s why simply changing the MIME type to audio/webm doesn’t work , it’s not actually converting the file to audio-only.

1 Like

Ah, I understand.

When I upload the test audio file I shared to my workflow, the Mime Type is still audio/webm. So, there must be an issue with the code in the nodes I’m currently using.

  1. “Code: Find binary data”: This node simply appends a .webm extension to the filename recorded in the form-data.

  2. “Code: Validate & Pick Binary1”: The code in this node forcibly changes the MIME Type based on the modified file extension received from the previous node.

Therefore, I need to change the process to call and use the audio file’s original extension and MIME type. Is there a way to get the necessary information, like a unique n8n function (similar to a function that gets a buffer)? I feel like if I can just solve this, I’ll be finished this issue!

The problem is that the second Code node is converting the type:

 case 'webm': return 'audio/webm';

This is incorrect, you don’t need to perform this conversion.
I’m not exactly sure what your use case is, but here’s the simplest version of your workflow without any code:

This should work as long as you pass the correct file format, as described by OpenAI..

In your case, you should pass a valid audio/webm voice clip, not a manually converted file.

Hope this helps @juhyeon_PARK !

2 Likes

After a lot of trial and error, I finally got my workflow working correctly. I fixed the issue by setting the audio file type to audio/webm on the browser side and, most importantly, by changing the settings in the “Webhook: Upload (POST)” node.

By turning the Binary File Option to OFF, the file information (like the filename and Mime Type) that was set in the browser was passed directly to the n8n server without any modification. This allowed me to use the file information as-is, eliminating the need for the intermediate “Code” nodes.

@mohamed3nan, Thank you so much for all your advice and help.

You’ve answered a lot of my questions :slight_smile:

1 Like

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