Handling WhatsApp Voice Notes in n8n

I built a simple n8n workflow to handle both text messages and voice notes coming from WhatsApp.

The reason was straightforward: most WhatsApp automations work fine with text, but real users often send voice notes, incomplete messages, or mixed-language input. That usually breaks the flow.

So I designed the workflow to first check the message type.

If it’s a text message, it goes directly for processing.
If it’s a voice note, the workflow fetches the audio file, transcribes it, and then processes the transcript the same way as text.

That keeps the logic clean and makes the system much more practical for real-world use.

What I like about this setup is that it adapts to how people naturally communicate instead of forcing them into a strict format.

This kind of workflow can be useful for:

  • ordering systems

  • support flows

  • appointment booking

  • procurement requests

For me, the main value here is not just sending an AI reply.
It’s turning messy input like voice notes into something structured enough to actually use inside a workflow.

Would love to see how others are handling audio-based WhatsApp use cases in n8n.

Good pattern. A few things that come up once voice notes are in production:

Transcription fallback: Short or noisy recordings often come back as gibberish or fail entirely. Worth wrapping the transcription step with error handling — if the result is under ~5 words or empty, send back “Sorry, I couldn’t understand that audio — could you type your message?” rather than passing garbage to the AI.

Media download timing: WhatsApp Cloud API media URLs expire after a few minutes. If your workflow has any delay between receiving the webhook and fetching the audio, you can hit a 401. Fetch the media binary immediately in the first node after the trigger, before any buffering or dedup logic.

Multilingual: as Benjamin mentioned, Whisper defaults to auto-detection but can be nudged with a language param if you know your user base (e.g., pt for Brazilian Portuguese). Cuts errors significantly for non-English voices.

Unified normalization: what you described — normalizing all types to a single content field at entry — is the right call. We run the same pattern in production: text gets passed through directly, voice gets transcribed into text, images get captioned. Same AI agent handles everything downstream.

Good points — I agree.

I’ll update the flow to fetch WhatsApp media immediately after the webhook, add fallback handling for short/failed transcriptions, and avoid sending bad audio text into the AI.

I’ll also keep the unified normalization pattern so text, voice, and images all become one clean content field before the AI agent.

One more production gotcha to add on the buffer side:

If your workflow buffers messages before passing them to the AI (common pattern to batch rapid messages), check how your buffer INSERT node references the message text. A frequent mistake after adding transcription: the buffer node still references `$(‘NORMALIZE’).first().json.text` — or whatever your early normalization node is called — bypassing the transcription step entirely. The transcription node runs and succeeds, but `[audio]` gets written to the buffer instead of the transcribed text. The LLM then receives `[audio]` as the message content.

Fix: use `$json.text` in the buffer INSERT instead of a hardcoded node reference. That way it always picks up the output of whatever node last transformed the message (the transcription SET node, in this case).

Also worth setting `onError: continueRegularOutput` on the transcription node with a fallback text like “Sorry, I couldn’t transcribe that audio” — so a transcription failure doesn’t break the whole flow.

Tested in production last week on a scheduling bot.

Good point, this is exactly the kind of issue that shows up only once the workflow starts handling real users.

I’ve seen the same problem in n8n when the buffer step keeps referencing an older normalization node instead of the latest transformed output. The transcription works fine, but the downstream node still receives [audio] or the original placeholder because the reference is hardcoded.

Using $json.text at the buffer insert stage makes much more sense because the workflow stays flexible whether the message started as text or voice. It also keeps the same buffer logic reusable after transcription, cleanup, or any future preprocessing step.

And yes, adding onError: continueRegularOutput with a fallback message is important for production. A failed transcription should not kill the whole conversation flow. It should fail gracefully and still let the bot respond properly.

Nice catch from production testing. These small reference issues are usually what make or break WhatsApp + AI workflows in real use.

Salut @automaxion,

En analysant ton workflow, j’aimerais te proposer une piste d’optimisation. Actuellement, ton flux se divise en deux branches parallèles qui contiennent chacune un nœud OpenAI (avec des outils) et un nœud HTTP Request final pour envoyer le message via 2Chat.

Je tepropose de simplifier et optimiser.

L’idée est de centraliser la logique en faisant converger les deux branches avant d’appeler l’IA. Voici comment procéder :

  1. La branche du haut (Texte) : Si le message est déjà du texte, le flux n’a rien à faire de spécial et peut simplement continuer.

  2. La branche du bas (Audio) : Si c’est un fichier audio, tu télécharges le fichier avec un nœud HTTP Request (configuré pour renvoyer un fichier binaire, par exemple sous la clé data), puis tu le transcris via le nœud OpenAI (Whisper) pour en faire du texte.

  3. Le nœud de regroupement (Normalisation) : Juste après ces deux branches, tu peux utiliser un nœud Merge (en mode Choose Branch ou Pass-through) ou un nœud Set (Edit Fields) pour harmoniser la variable de texte. Par exemple, tu peux définir une variable message_utilisateur avec cette formule simple :
    {{ $json.text || $(‘OpenAI Transcribe’).item.json.text }}
    (Cette formule prendra le texte du message s’il existe, sinon elle récupérera la transcription de l’audio).

  4. La suite du flux (Unique) : Tu connectes ensuite cette variable à un seul nœud OpenAI (Message Model) qui s’occupe de la logique de réponse, puis à un seul nœud HTTP Request de sortie pour renvoyer le message sur WhatsApp.

Cette structure réduit ton nombre de nœuds, économise tes ressources et rend ton système beaucoup plus robuste face aux futures modifications.

En espérant que ces explications t’aideront à optimiser et simplifier ton flux d’automatisation WhatsApp. Si ce message t’a permis de mieux comprendre ou de perfectionner ton workflow, n’hésite pas à le marquer comme solution pour que cela puisse aider d’autres membres de la communauté dans la même situation.!

Thanks for the detailed walkthrough! Centralizing the logic before the AI node is a smart call, and the fallback formula for handling text vs. audio transcription is exactly the kind of cleanup I was after. I’ll rework it down to a single OpenAI + HTTP Request path and give it a test. Marking as solution really helpful, thanks!

If my answer helped you, could you mark it as the solution and click the heart icon?